Update base for Update on "compiler: super early exploration of instruction reordering"

See comments in InstructionReordering.ts. This needs substantial iteration before landing in some form, just putting up to share for discussion.

[ghstack-poisoned]
This commit is contained in:
Joe Savona
2024-05-29 16:26:32 -07:00
175 changed files with 2204 additions and 2036 deletions
+1
View File
@@ -0,0 +1 @@
c998bb1ed4b3285398c9c7797135d3f060243c6a
+20 -2
View File
@@ -24,6 +24,25 @@ jobs:
run: echo "matrix=$(find packages -mindepth 1 -maxdepth 1 -type d | sed 's!packages/!!g' | tr '\n' ',' | sed s/.$// | jq -Rsc '. / "," - [""]')" >> $GITHUB_OUTPUT
# Hardcoded to improve parallelism for babel-plugin-react-compiler
prettier:
name: Run prettier
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn prettier:ci
# Hardcoded to improve parallelism
lint:
name: Lint babel-plugin-react-compiler
runs-on: ubuntu-latest
@@ -40,10 +59,9 @@ jobs:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler prettier:ci
- run: yarn workspace babel-plugin-react-compiler lint
# Hardcoded to improve parallelism for babel-plugin-react-compiler
# Hardcoded to improve parallelism
jest:
name: Jest babel-plugin-react-compiler
runs-on: ubuntu-latest
@@ -82,11 +82,12 @@ module.exports = {
],
"@typescript-eslint/array-type": ["error", { default: "generic" }],
"@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/no-var-requires": "off"
},
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
root: true,
ignorePatterns: ["src/__tests__/**/*", "src/**/*.d.ts", "dist/**/*"],
ignorePatterns: ["**/__tests__/**/*", "**/*.d.ts", "**/dist/**/*"],
env: {
node: true,
},
-2
View File
@@ -1,2 +0,0 @@
741ae6e3aa10735722a442a3c8be77af7c951204
59cba458af27d936df8feabf641f545f431529ad
+14 -5
View File
@@ -1,12 +1,21 @@
.fixtures/
bench/
**/dist
**/__tests__/fixtures/**/*.expect.md
**/__tests__/fixtures/**/*.flow.js
**/.next
test262/
*.md
crates
apps/playground/public
**/LICENSE
.*
*.md*
*.json
*.css
*.webmanifest
packages/js-fuzzer
*.map
*.sh
*.txt
*.ico
*.svg
*.lock
*.toml
+9
View File
@@ -0,0 +1,9 @@
const config = {
requirePragma: false,
parser: "babel-ts",
semi: true,
singleQuote: false,
trailingComma: "es5"
}
module.exports = config;
-4
View File
@@ -1,4 +0,0 @@
{
"requirePragma": false,
"parser": "babel-ts"
}
-1
View File
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
/**
* Sync from <https://github.com/reactjs/reactjs.org/blob/main/beta/colors.js>.
*/
@@ -43,7 +43,7 @@ import {
} from "./Output";
function parseFunctions(
source: string,
source: string
): Array<
NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
@@ -148,7 +148,7 @@ function isHookName(s: string): boolean {
}
function getReactFunctionType(
id: NodePath<t.Identifier | null | undefined>,
id: NodePath<t.Identifier | null | undefined>
): ReactFunctionType {
if (id && id.node && id.isIdentifier()) {
if (isHookName(id.node.name)) {
@@ -189,7 +189,7 @@ function compile(source: string): CompilerOutput {
severity: ErrorSeverity.Todo,
loc: fn.node.loc ?? null,
suggestions: null,
}),
})
);
continue;
}
@@ -205,7 +205,7 @@ function compile(source: string): CompilerOutput {
"_c",
null,
null,
null,
null
)) {
const fnName = fn.node.id?.name ?? null;
switch (result.kind) {
@@ -274,7 +274,7 @@ function compile(source: string): CompilerOutput {
reason: `Unexpected failure when transforming input! ${err}`,
loc: null,
suggestions: null,
}),
})
);
}
}
@@ -291,7 +291,7 @@ export default function Editor() {
const { enqueueSnackbar } = useSnackbar();
const compilerOutput = useMemo(
() => compile(deferredStore.source),
[deferredStore.source],
[deferredStore.source]
);
useMountEffect(() => {
@@ -305,7 +305,7 @@ export default function Editor() {
...createMessage(
"Bad URL - fell back to the default Playground.",
MessageLevel.Info,
MessageSource.Playground,
MessageSource.Playground
),
});
mountStore = defaultStore;
@@ -319,9 +319,7 @@ export default function Editor() {
return (
<>
<div className="relative flex basis top-14">
<div
className={clsx("relative sm:basis-1/4")}
>
<div className={clsx("relative sm:basis-1/4")}>
<Input
errors={
compilerOutput.kind === "err" ? compilerOutput.error.details : []
@@ -76,7 +76,7 @@ export default function Input({ errors }: Props) {
allowSyntheticDefaultImports: true,
};
monaco.languages.typescript.javascriptDefaults.setCompilerOptions(
tscOptions,
tscOptions
);
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
...tscOptions,
@@ -106,7 +106,7 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
output={text}
diff={lastPassOutput ?? null}
showInfoPanel={true}
></TextTabContent>,
></TextTabContent>
);
lastPassOutput = text;
}
@@ -122,7 +122,7 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
output={code}
diff={null}
showInfoPanel={false}
></TextTabContent>,
></TextTabContent>
);
if (sourceMapUrl) {
reorderedTabs.set(
@@ -133,7 +133,7 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
className="w-full h-monaco_small sm:h-monaco"
title="Generated Code"
/>
</>,
</>
);
}
}
@@ -145,16 +145,16 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
async function codegen(
ast: t.Program,
source: string,
source: string
): Promise<{ code: any; sourceMapUrl: string | null }> {
const generated = generate(
ast,
{ sourceMaps: true, sourceFileName: "input.js" },
source,
source
);
const sourceMapUrl = getSourceMapUrl(
generated.code,
JSON.stringify(generated.map),
JSON.stringify(generated.map)
);
const codegenOutput = await prettier.format(generated.code, {
semi: true,
@@ -172,14 +172,14 @@ function getSourceMapUrl(code: string, map: string): string | null {
code = utf16ToUTF8(code);
map = utf16ToUTF8(map);
return `https://evanw.github.io/source-map-visualization/#${btoa(
`${code.length}\0${code}${map.length}\0${map}`,
`${code.length}\0${code}${map.length}\0${map}`
)}`;
}
function Output({ store, compilerOutput }: Props) {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(['JS']));
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(["JS"]));
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
() => new Map(),
() => new Map()
);
useEffect(() => {
tabify(store.source, compilerOutput).then((tabs) => {
@@ -13,4 +13,4 @@ const Editor = dynamic(() => import("./EditorImpl"), {
ssr: false,
});
export default Editor;
export default Editor;
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import type { EditorProps } from "@monaco-editor/react";
export const monacoOptions: Partial<EditorProps["options"]> = {
@@ -50,7 +50,7 @@ export default function Header() {
<Logo
className={clsx(
"w-8 h-8 text-link",
process.env.NODE_ENV === "development" && "text-yellow-600",
process.env.NODE_ENV === "development" && "text-yellow-600"
)}
/>
<p className="hidden select-none sm:block">React Compiler Playground</p>
@@ -21,5 +21,5 @@ export const IconGitHub = memo<JSX.IntrinsicElements["svg"]>(
<path d="M10 0a10 10 0 0 0-3.16 19.49c.5.1.68-.22.68-.48l-.01-1.7c-2.78.6-3.37-1.34-3.37-1.34-.46-1.16-1.11-1.47-1.11-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.1-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.1.39-1.99 1.03-2.69a3.6 3.6 0 0 1 .1-2.64s.84-.27 2.75 1.02a9.58 9.58 0 0 1 5 0c1.91-1.3 2.75-1.02 2.75-1.02.55 1.37.2 2.4.1 2.64.64.7 1.03 1.6 1.03 2.69 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85l-.01 2.75c0 .26.18.58.69.48A10 10 0 0 0 10 0"></path>
</svg>
);
},
}
);
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
// https://github.com/reactjs/reactjs.org/blob/main/beta/src/components/Logo.tsx
export default function Logo(props: JSX.IntrinsicElements["svg"]) {
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import type { Dispatch, ReactNode } from "react";
import { useReducer } from "react";
import createContext from "../lib/createContext";
@@ -78,7 +78,7 @@ function TabbedWindowItem({
title="Minimize tab"
aria-label="Minimize tab"
onClick={toggleTabs}
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${hasChanged ? 'font-bold' : 'font-light'} text-secondary hover:text-link`}
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${hasChanged ? "font-bold" : "font-light"} text-secondary hover:text-link`}
>
- {name}
</h2>
@@ -91,7 +91,7 @@ function TabbedWindowItem({
aria-label={`Expand compiler tab: ${name}`}
style={{ transform: "rotate(90deg) translate(-50%)" }}
onClick={toggleTabs}
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${hasChanged ? 'font-bold' : 'font-light'} text-secondary hover:text-link`}
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${hasChanged ? "font-bold" : "font-light"} text-secondary hover:text-link`}
>
{name}
</button>
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
export { default as Editor } from "./Editor";
export { default as Header } from "./Header";
export { StoreProvider } from "./StoreContext";
-1
View File
@@ -5,5 +5,4 @@
* LICENSE file in the root directory of this source tree.
*/
export { default as useMountEffect } from "./useMountEffect";
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import type { EffectCallback } from "react";
import { useEffect } from "react";
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import React from "react";
/**
@@ -14,7 +14,7 @@ import { MarkerSeverity, type editor } from "monaco-editor";
function mapReactCompilerSeverityToMonaco(
level: ErrorSeverity,
monaco: Monaco,
monaco: Monaco
): MarkerSeverity {
switch (level) {
case ErrorSeverity.Todo:
@@ -26,7 +26,7 @@ function mapReactCompilerSeverityToMonaco(
function mapReactCompilerDiagnosticToMonacoMarker(
detail: CompilerErrorDetail,
monaco: Monaco,
monaco: Monaco
): editor.IMarkerData | null {
if (detail.loc == null || typeof detail.loc === "symbol") {
return null;
@@ -70,7 +70,7 @@ export function renderReactCompilerMarkers({
marker.startLineNumber,
marker.startColumn,
marker.endLineNumber,
marker.endColumn,
marker.endColumn
),
options: {
isWholeLine: true,
@@ -83,7 +83,7 @@ export function renderReactCompilerMarkers({
monaco.editor.setModelMarkers(model, "owner", []);
decorations = model.deltaDecorations(
model.getAllDecorations().map((d) => d.id),
[],
[]
);
}
}
@@ -5,6 +5,5 @@
* LICENSE file in the root directory of this source tree.
*/
export * from "./messages";
export * from "./store";
@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
export enum MessageSource {
Babel,
Forget,
+2 -2
View File
@@ -23,7 +23,7 @@ const nextConfig = {
new MonacoWebpackPlugin({
languages: ["typescript", "javascript"],
filename: "static/[name].worker.js",
}),
})
);
}
@@ -31,7 +31,7 @@ const nextConfig = {
...config.resolve.alias,
"react-compiler-runtime": path.resolve(
__dirname,
"../../packages/react-compiler-runtime",
"../../packages/react-compiler-runtime"
),
};
+3
View File
@@ -24,6 +24,8 @@
"build": "yarn workspaces run build",
"dev": "concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && yarn workspace playground run dev\"",
"test": "yarn workspaces run test",
"prettier:write": "prettier --write . --log-level=warn",
"prettier:ci": "prettier --check . --log-level=warn",
"snap": "yarn workspace babel-plugin-react-compiler run snap",
"snap:build": "yarn workspace snap run build",
"postinstall": "perl -p -i -e 's/react\\.element/react.transitional.element/' packages/snap/node_modules/fbt/lib/FbtReactUtil.js && perl -p -i -e 's/didWarnAboutUsingAct = false;/didWarnAboutUsingAct = true;/' packages/babel-plugin-react-compiler/node_modules/react-dom/cjs/react-dom-test-utils.development.js",
@@ -40,6 +42,7 @@
"concurrently": "^7.4.0",
"folder-hash": "^4.0.4",
"ora": "5.4.1",
"prettier": "^3.2.5",
"prompt-promise": "^1.0.3",
"rollup": "^4.13.2",
"rollup-plugin-banner2": "^1.2.3",
@@ -1,5 +0,0 @@
**/dist
**/__tests__/fixtures/**/*.expect.md
**/__tests__/fixtures/**/*.flow.js
*.md
*.json
@@ -1,4 +0,0 @@
{
"requirePragma": false,
"parser": "babel-ts"
}
@@ -1,6 +1,6 @@
{
"name": "babel-plugin-react-compiler",
"version": "0.0.0-experimental-592953e-20240517",
"version": "0.0.0-experimental-487cb0e-20240529",
"description": "Babel plugin for React Compiler.",
"main": "dist/index.js",
"license": "MIT",
@@ -15,9 +15,6 @@
"snap:build": "yarn workspace snap run build",
"snap:ci": "yarn snap:build && yarn snap",
"ts:analyze-trace": "scripts/ts-analyze-trace.sh",
"prettier": "node ./scripts/prettier.js write-changed",
"prettier:all": "node ./scripts/prettier.js write",
"prettier:ci": "prettier --check .",
"lint": "yarn eslint src"
},
"dependencies": {
@@ -53,7 +50,6 @@
"glob": "^7.1.6",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"prettier": "2.8.8",
"react": "19.0.0-beta-b498834eab-20240506",
"react-dom": "19.0.0-beta-b498834eab-20240506",
"rimraf": "^3.0.2",
@@ -1,79 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"use strict";
/*
* Based on similar script in React
* https://github.com/facebook/react/blob/main/scripts/prettier/index.js
*/
const chalk = require("chalk");
const glob = require("glob");
const prettier = require("prettier");
const fs = require("fs");
const listChangedFiles = require("./shared/list-changed-files");
const prettierConfigPath = require.resolve("../.prettierrc");
const mode = process.argv[2] || "check";
const shouldWrite = mode === "write" || mode === "write-changed";
const onlyChanged = mode === "check-changed" || mode === "write-changed";
const changedFiles = onlyChanged ? listChangedFiles() : null;
let didWarn = false;
let didError = false;
const files = glob
.sync("**/*.{js,ts,tsx,jsx}", {
ignore: ["**/node_modules/**", "**/__tests__/fixtures/**/*.flow.js"],
})
.filter((f) => !onlyChanged || changedFiles.has(f));
if (!files.length) {
return;
}
files.forEach((file) => {
const options = prettier.resolveConfig.sync(file, {
config: prettierConfigPath,
});
try {
const input = fs.readFileSync(file, "utf8");
if (shouldWrite) {
const output = prettier.format(input, options);
if (output !== input) {
fs.writeFileSync(file, output, "utf8");
}
} else {
if (!prettier.check(input, options)) {
if (!didWarn) {
console.log(
"\n" +
chalk.red(
` This project uses prettier to format all JavaScript code.\n`
) +
chalk.dim(` Please run `) +
chalk.reset("yarn prettier:all") +
chalk.dim(
` and add changes to files listed below to your commit:`
) +
`\n\n`
);
didWarn = true;
}
console.log(file);
}
}
} catch (error) {
didError = true;
console.log("\n\n" + error.message);
console.log(file);
}
});
if (didWarn || didError) {
process.exitCode = 1;
}
@@ -1,39 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"use strict";
const execFileSync = require("child_process").execFileSync;
const exec = (command, args) => {
console.log("> " + [command].concat(args).join(" "));
const options = {
cwd: process.cwd(),
env: process.env,
stdio: "pipe",
encoding: "utf-8",
};
return execFileSync(command, args, options);
};
const execGitCmd = (args) => exec("git", args).trim().toString().split("\n");
const listChangedFiles = () => {
const mergeBase = execGitCmd(["merge-base", "HEAD", "main"]);
return new Set([
...execGitCmd([
"diff",
"--name-only",
"--relative",
"--diff-filter=ACMRTUB",
mergeBase,
]),
...execGitCmd(["ls-files", "--others", "--exclude-standard"]),
]);
};
module.exports = listChangedFiles;
@@ -36,6 +36,8 @@ export function runBabelPluginReactCompiler(
"babel-plugin-fbt-runtime",
],
sourceType: "module",
configFile: false,
babelrc: false,
});
invariant(
result?.code != null,
@@ -1520,6 +1520,15 @@ function lowerExpression(
place,
});
} else if (propertyPath.isObjectMethod()) {
if (propertyPath.node.kind !== "method") {
builder.errors.push({
reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.node.kind} functions in ObjectExpression`,
severity: ErrorSeverity.Todo,
loc: propertyPath.node.loc ?? null,
suggestions: null,
});
continue;
}
const method = lowerObjectMethod(builder, propertyPath);
const place = lowerValueToTemporary(builder, method);
const loweredKey = lowerObjectPropertyKey(builder, propertyPath);
@@ -3336,6 +3345,20 @@ function lowerIdentifierForAssignment(
});
return null;
}
} else if (
binding.bindingKind === "const" &&
kind === InstructionKind.Reassign
) {
builder.errors.push({
reason: `Cannot reassign a \`const\` variable`,
severity: ErrorSeverity.InvalidJS,
loc: path.node.loc ?? null,
description:
binding.identifier.name != null
? `\`${binding.identifier.name.value}\` is declared as const`
: null,
});
return null;
}
const place: Place = {
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import { BindingKind } from "@babel/traverse";
import * as t from "@babel/types";
import { CompilerError, CompilerErrorDetailOptions } from "../CompilerError";
import { assertExhaustive } from "../Utils/utils";
@@ -77,7 +78,7 @@ export type ReactiveInstructionStatement = {
};
export type ReactiveTerminalStatement<
Tterminal extends ReactiveTerminal = ReactiveTerminal
Tterminal extends ReactiveTerminal = ReactiveTerminal,
> = {
kind: "terminal";
terminal: Tterminal;
@@ -1105,7 +1106,7 @@ export type MutableRange = {
export type VariableBinding =
// let, const, etc declared within the current component/hook
| { kind: "Identifier"; identifier: Identifier }
| { kind: "Identifier"; identifier: Identifier; bindingKind: BindingKind }
// bindings declard outside the current component/hook
| NonLocalBinding;
@@ -280,7 +280,11 @@ export default class HIRBuilder {
if (resolvedBinding.name && resolvedBinding.name.value !== originalName) {
babelBinding.scope.rename(originalName, resolvedBinding.name.value);
}
return { kind: "Identifier", identifier: resolvedBinding };
return {
kind: "Identifier",
identifier: resolvedBinding,
bindingKind: babelBinding.kind,
};
}
isContextIdentifier(path: NodePath<t.Identifier | t.JSXIdentifier>): boolean {
@@ -431,6 +431,9 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
["*", { kind: "Object", shapeId: BuiltInMixedReadonlyId }],
]);
addObject(BUILTIN_SHAPES, BuiltInJsxId, []);
addObject(BUILTIN_SHAPES, BuiltInFunctionId, []);
export const DefaultMutatingHook = addHook(
BUILTIN_SHAPES,
{
@@ -876,7 +876,7 @@ export function mapTerminalSuccessors(
export function terminalHasFallthrough<
T extends Terminal,
U extends T & { fallthrough: BlockId }
U extends T & { fallthrough: BlockId },
>(terminal: T): terminal is U {
switch (terminal.kind) {
case "maybe-throw":
@@ -1103,13 +1103,56 @@ function inferBlock(
break;
}
case "JsxExpression": {
valueKind = {
if (instrValue.tag.kind === "Identifier") {
state.referenceAndRecordEffects(
instrValue.tag,
Effect.Freeze,
ValueReason.JsxCaptured,
functionEffects
);
}
if (instrValue.children !== null) {
for (const child of instrValue.children) {
state.referenceAndRecordEffects(
child,
Effect.Freeze,
ValueReason.JsxCaptured,
functionEffects
);
}
}
for (const attr of instrValue.props) {
if (attr.kind === "JsxSpreadAttribute") {
state.referenceAndRecordEffects(
attr.argument,
Effect.Freeze,
ValueReason.JsxCaptured,
functionEffects
);
} else {
const propEffects: Array<FunctionEffect> = [];
state.referenceAndRecordEffects(
attr.place,
Effect.Freeze,
ValueReason.JsxCaptured,
propEffects
);
functionEffects.push(
...propEffects.filter(
(propEffect) => propEffect.kind !== "GlobalMutation"
)
);
}
}
state.initialize(instrValue, {
kind: ValueKind.Frozen,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
effect = { kind: Effect.Freeze, reason: ValueReason.JsxCaptured };
break;
});
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.ConditionallyMutate;
continue;
}
case "JsxFragment": {
valueKind = {
@@ -311,6 +311,25 @@ function evaluateInstruction(
}
return null;
}
case "UnaryExpression": {
switch (value.operator) {
case "!": {
const operand = read(constants, value.value);
if (operand !== null && operand.kind === "Primitive") {
const result: Primitive = {
kind: "Primitive",
value: !operand.value,
loc: value.loc,
};
instr.value = result;
return result;
}
return null;
}
default:
return null;
}
}
case "BinaryExpression": {
const lhsValue = read(constants, value.left);
const rhsValue = read(constants, value.right);
@@ -327,6 +346,8 @@ function evaluateInstruction(
case "+": {
if (typeof lhs === "number" && typeof rhs === "number") {
result = { kind: "Primitive", value: lhs + rhs, loc: value.loc };
} else if (typeof lhs === "string" && typeof rhs === "string") {
result = { kind: "Primitive", value: lhs + rhs, loc: value.loc };
}
break;
}
@@ -81,18 +81,21 @@ export function codegenFunction(
);
/**
* Hot-module reloading reuses component instances at runtime even as the source of the component changes.
* Fast Refresh reuses component instances at runtime even as the source of the component changes.
* The generated code needs to prevent values from one version of the code being reused after a code cange.
* If HMR detection is enabled and we know the source code of the component, assign a cache slot to track
* the source hash, and later, emit code to check for source changes and reset the cache on source changes.
*/
let hotModuleReloadState: { cacheIndex: number; hash: string } | null = null;
let fastRefreshState: {
cacheIndex: number;
hash: string;
} | null = null;
if (
fn.env.config.enableResetCacheOnSourceFileChanges &&
fn.env.code !== null
) {
const hash = createHmac("sha256", fn.env.code).digest("hex");
hotModuleReloadState = {
fastRefreshState = {
cacheIndex: cx.nextCacheIndex,
hash,
};
@@ -131,7 +134,7 @@ export function codegenFunction(
),
])
);
if (hotModuleReloadState !== null) {
if (fastRefreshState !== null) {
// HMR detection is enabled, emit code to reset the memo cache on source changes
const index = cx.synthesizeName("$i");
preface.push(
@@ -140,10 +143,10 @@ export function codegenFunction(
"!==",
t.memberExpression(
t.identifier(cx.synthesizeName("$")),
t.numericLiteral(hotModuleReloadState.cacheIndex),
t.numericLiteral(fastRefreshState.cacheIndex),
true
),
t.stringLiteral(hotModuleReloadState.hash)
t.stringLiteral(fastRefreshState.hash)
),
t.blockStatement([
t.forStatement(
@@ -185,10 +188,10 @@ export function codegenFunction(
"=",
t.memberExpression(
t.identifier(cx.synthesizeName("$")),
t.numericLiteral(hotModuleReloadState.cacheIndex),
t.numericLiteral(fastRefreshState.cacheIndex),
true
),
t.stringLiteral(hotModuleReloadState.hash)
t.stringLiteral(fastRefreshState.hash)
)
),
])
@@ -257,7 +257,7 @@ export type Transformed<T> =
| { kind: "replace-many"; value: Array<T> };
export class ReactiveFunctionTransform<
TState = void
TState = void,
> extends ReactiveFunctionVisitor<TState> {
override traverseBlock(block: ReactiveBlock, state: TState): void {
let nextBlock: ReactiveBlock | null = null;
@@ -10,6 +10,7 @@ import {
HIRFunction,
IdentifierId,
Place,
SourceLocation,
isRefValueType,
isUseRefType,
} from "../HIR";
@@ -117,7 +118,12 @@ function validateNoRefAccessInRenderImpl(
case "MethodCall": {
if (!isEffectHook(instr.value.property.identifier)) {
for (const operand of eachInstructionValueOperand(instr.value)) {
validateNoRefAccess(errors, refAccessingFunctions, operand);
validateNoRefAccess(
errors,
refAccessingFunctions,
operand,
operand.loc
);
}
}
break;
@@ -138,7 +144,12 @@ function validateNoRefAccessInRenderImpl(
});
}
for (const operand of eachInstructionValueOperand(instr.value)) {
validateNoRefAccess(errors, refAccessingFunctions, operand);
validateNoRefAccess(
errors,
refAccessingFunctions,
operand,
operand.loc
);
}
}
break;
@@ -146,7 +157,30 @@ function validateNoRefAccessInRenderImpl(
case "ObjectExpression":
case "ArrayExpression": {
for (const operand of eachInstructionValueOperand(instr.value)) {
validateNoRefAccess(errors, refAccessingFunctions, operand);
validateNoRefAccess(
errors,
refAccessingFunctions,
operand,
operand.loc
);
}
break;
}
case "PropertyDelete":
case "PropertyStore":
case "ComputedDelete":
case "ComputedStore": {
validateNoRefAccess(
errors,
refAccessingFunctions,
instr.value.object,
instr.loc
);
for (const operand of eachInstructionValueOperand(instr.value)) {
if (operand === instr.value.object) {
continue;
}
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
}
break;
}
@@ -172,12 +206,12 @@ function validateNoRefAccessInRenderImpl(
function validateNoRefValueAccess(
errors: CompilerError,
unconditionalSetStateFunctions: Set<IdentifierId>,
refAccessingFunctions: Set<IdentifierId>,
operand: Place
): void {
if (
isRefValueType(operand.identifier) ||
unconditionalSetStateFunctions.has(operand.identifier.id)
refAccessingFunctions.has(operand.identifier.id)
) {
errors.push({
severity: ErrorSeverity.InvalidReact,
@@ -192,20 +226,25 @@ function validateNoRefValueAccess(
function validateNoRefAccess(
errors: CompilerError,
unconditionalSetStateFunctions: Set<IdentifierId>,
operand: Place
refAccessingFunctions: Set<IdentifierId>,
operand: Place,
loc: SourceLocation
): void {
if (
isRefValueType(operand.identifier) ||
isUseRefType(operand.identifier) ||
unconditionalSetStateFunctions.has(operand.identifier.id)
refAccessingFunctions.has(operand.identifier.id)
) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason:
"Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)",
loc: operand.loc,
description: `Cannot access ref value at ${printPlace(operand)}`,
loc: loc,
description:
operand.identifier.name !== null &&
operand.identifier.name.kind === "named"
? `Cannot access ref value \`${operand.identifier.name.value}\``
: null,
suggestions: null,
});
}
@@ -24,8 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
/**
import { c as _c } from "react/compiler-runtime"; /**
* This is a weird case as data has type `BuiltInMixedReadonly`.
* The only scoped value we currently infer in this program is the
* PropertyLoad `data?.toString`.
@@ -0,0 +1,86 @@
## Input
```javascript
import { useMemo } from "react";
const someGlobal = { value: 0 };
function Component({ value }) {
const onClick = () => {
someGlobal.value = value;
};
return useMemo(() => {
return <div onClick={onClick}>{someGlobal.value}</div>;
}, []);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 0 }],
sequentialRenders: [
{ value: 1 },
{ value: 1 },
{ value: 42 },
{ value: 42 },
{ value: 0 },
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
const someGlobal = { value: 0 };
function Component(t0) {
const $ = _c(4);
const { value } = t0;
let t1;
if ($[0] !== value) {
t1 = () => {
someGlobal.value = value;
};
$[0] = value;
$[1] = t1;
} else {
t1 = $[1];
}
const onClick = t1;
let t2;
let t3;
if ($[2] !== onClick) {
t3 = <div onClick={onClick}>{someGlobal.value}</div>;
$[2] = onClick;
$[3] = t3;
} else {
t3 = $[3];
}
t2 = t3;
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 0 }],
sequentialRenders: [
{ value: 1 },
{ value: 1 },
{ value: 42 },
{ value: 42 },
{ value: 0 },
],
};
```
### Eval output
(kind: ok) <div>0</div>
<div>0</div>
<div>0</div>
<div>0</div>
<div>0</div>
@@ -0,0 +1,24 @@
import { useMemo } from "react";
const someGlobal = { value: 0 };
function Component({ value }) {
const onClick = () => {
someGlobal.value = value;
};
return useMemo(() => {
return <div onClick={onClick}>{someGlobal.value}</div>;
}, []);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 0 }],
sequentialRenders: [
{ value: 1 },
{ value: 1 },
{ value: 42 },
{ value: 42 },
{ value: 0 },
],
};
@@ -0,0 +1,53 @@
## Input
```javascript
function Component() {
const onClick = () => {
// Cannot assign to globals
someUnknownGlobal = true;
moduleLocal = true;
};
// It's possible that this could be an event handler / effect function,
// but we don't know that and optimistically assume it will only be
// called by an event handler or effect, where it is allowed to modify globals
return <div onClick={onClick} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Component() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const onClick = () => {
someUnknownGlobal = true;
moduleLocal = true;
};
t0 = <div onClick={onClick} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
### Eval output
(kind: ok) <div></div>
@@ -0,0 +1,16 @@
function Component() {
const onClick = () => {
// Cannot assign to globals
someUnknownGlobal = true;
moduleLocal = true;
};
// It's possible that this could be an event handler / effect function,
// but we don't know that and optimistically assume it will only be
// called by an event handler or effect, where it is allowed to modify globals
return <div onClick={onClick} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
@@ -0,0 +1,93 @@
## Input
```javascript
import { Stringify } from "shared-runtime";
/**
* We currently hoist the accessed properties of function expressions,
* regardless of control flow. This is simply because we wrote support for
* function expressions before doing a lot of work in PropagateScopeDeps
* to handle conditionally accessed dependencies.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div>{"shouldInvokeFns":true,"callback":{"kind":"Function","result":null}}</div>
* Forget:
* (kind: exception) Cannot read properties of null (reading 'prop')
*/
function Component({ obj, isObjNull }) {
const callback = () => {
if (!isObjNull) {
return obj.prop;
} else {
return null;
}
};
return <Stringify shouldInvokeFns={true} callback={callback} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ obj: null, isObjNull: true }],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
/**
* We currently hoist the accessed properties of function expressions,
* regardless of control flow. This is simply because we wrote support for
* function expressions before doing a lot of work in PropagateScopeDeps
* to handle conditionally accessed dependencies.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div>{"shouldInvokeFns":true,"callback":{"kind":"Function","result":null}}</div>
* Forget:
* (kind: exception) Cannot read properties of null (reading 'prop')
*/
function Component(t0) {
const $ = _c(5);
const { obj, isObjNull } = t0;
let t1;
if ($[0] !== isObjNull || $[1] !== obj.prop) {
t1 = () => {
if (!isObjNull) {
return obj.prop;
} else {
return null;
}
};
$[0] = isObjNull;
$[1] = obj.prop;
$[2] = t1;
} else {
t1 = $[2];
}
const callback = t1;
let t2;
if ($[3] !== callback) {
t2 = <Stringify shouldInvokeFns={true} callback={callback} />;
$[3] = callback;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ obj: null, isObjNull: true }],
};
```
@@ -0,0 +1,30 @@
import { Stringify } from "shared-runtime";
/**
* We currently hoist the accessed properties of function expressions,
* regardless of control flow. This is simply because we wrote support for
* function expressions before doing a lot of work in PropagateScopeDeps
* to handle conditionally accessed dependencies.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div>{"shouldInvokeFns":true,"callback":{"kind":"Function","result":null}}</div>
* Forget:
* (kind: exception) Cannot read properties of null (reading 'prop')
*/
function Component({ obj, isObjNull }) {
const callback = () => {
if (!isObjNull) {
return obj.prop;
} else {
return null;
}
};
return <Stringify shouldInvokeFns={true} callback={callback} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ obj: null, isObjNull: true }],
};
@@ -0,0 +1,119 @@
## Input
```javascript
import invariant from "invariant";
import {
makeObject_Primitives,
mutate,
sum,
useIdentity,
} from "shared-runtime";
/**
* Exposes fundamental issue with pruning 'non-reactive' dependencies + flattening
* those scopes. Here, `z`'s original memo block is removed due to the inner hook call.
* However, we also infer that `z` is non-reactive and does not need to be a memo
* dependency.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* Forget:
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [[ (exception in render) Invariant Violation: oh no! ]]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
*/
function MyApp({ count }) {
const z = makeObject_Primitives();
const x = useIdentity(2);
const y = sum(x, count);
mutate(z);
const thing = [y, z];
if (thing[1] !== z) {
invariant(false, "oh no!");
}
return thing;
}
export const FIXTURE_ENTRYPOINT = {
fn: MyApp,
params: [{ count: 2 }],
sequentialRenders: [{ count: 2 }, { count: 2 }, { count: 3 }],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import invariant from "invariant";
import {
makeObject_Primitives,
mutate,
sum,
useIdentity,
} from "shared-runtime";
/**
* Exposes fundamental issue with pruning 'non-reactive' dependencies + flattening
* those scopes. Here, `z`'s original memo block is removed due to the inner hook call.
* However, we also infer that `z` is non-reactive and does not need to be a memo
* dependency.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* Forget:
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [[ (exception in render) Invariant Violation: oh no! ]]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
*/
function MyApp(t0) {
const $ = _c(5);
const { count } = t0;
const z = makeObject_Primitives();
const x = useIdentity(2);
let t1;
if ($[0] !== x || $[1] !== count) {
t1 = sum(x, count);
$[0] = x;
$[1] = count;
$[2] = t1;
} else {
t1 = $[2];
}
const y = t1;
mutate(z);
let t2;
if ($[3] !== y) {
t2 = [y, z];
$[3] = y;
$[4] = t2;
} else {
t2 = $[4];
}
const thing = t2;
if (thing[1] !== z) {
invariant(false, "oh no!");
}
return thing;
}
export const FIXTURE_ENTRYPOINT = {
fn: MyApp,
params: [{ count: 2 }],
sequentialRenders: [{ count: 2 }, { count: 2 }, { count: 3 }],
};
```
@@ -0,0 +1,43 @@
import invariant from "invariant";
import {
makeObject_Primitives,
mutate,
sum,
useIdentity,
} from "shared-runtime";
/**
* Exposes fundamental issue with pruning 'non-reactive' dependencies + flattening
* those scopes. Here, `z`'s original memo block is removed due to the inner hook call.
* However, we also infer that `z` is non-reactive and does not need to be a memo
* dependency.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* Forget:
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [[ (exception in render) Invariant Violation: oh no! ]]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
*/
function MyApp({ count }) {
const z = makeObject_Primitives();
const x = useIdentity(2);
const y = sum(x, count);
mutate(z);
const thing = [y, z];
if (thing[1] !== z) {
invariant(false, "oh no!");
}
return thing;
}
export const FIXTURE_ENTRYPOINT = {
fn: MyApp,
params: [{ count: 2 }],
sequentialRenders: [{ count: 2 }, { count: 2 }, { count: 3 }],
};
@@ -76,7 +76,7 @@ function getNativeLogFunction(level) {
INSPECTOR_LEVELS[logLevel],
str,
[].slice.call(arguments),
INSPECTOR_FRAMES_TO_SKIP
INSPECTOR_FRAMES_TO_SKIP,
);
}
if (groupStack.length) {
@@ -46,7 +46,7 @@ function Component() {
x = { f: () => console.log("original") };
(console.log("A"), x)[(console.log("B"), "f")](
(changeF(x), console.log("arg"), 1)
(changeF(x), console.log("arg"), 1),
);
$[1] = x;
} else {
@@ -29,8 +29,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
/**
import { c as _c } from "react/compiler-runtime"; /**
* props.b *does* influence `a`
*/
function Component(props) {
@@ -66,8 +66,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
/**
import { c as _c } from "react/compiler-runtime"; /**
* props.b does *not* influence `a`
*/
function ComponentA(props) {
@@ -0,0 +1,35 @@
## Input
```javascript
function foo() {
const a = "a" + "b";
const c = "c";
return a + c;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};
```
## Code
```javascript
function foo() {
return "abc";
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};
```
### Eval output
(kind: ok) "abc"
@@ -0,0 +1,11 @@
function foo() {
const a = "a" + "b";
const c = "c";
return a + c;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};
@@ -0,0 +1,86 @@
## Input
```javascript
import { Stringify } from "shared-runtime";
function foo() {
let _b;
const b = true;
if (!b) {
_b = "bar";
} else {
_b = "baz";
}
return (
<Stringify
value={{
_b,
b0: !true,
n0: !0,
n1: !1,
n2: !2,
n3: !-1,
s0: !"",
s1: !"a",
s2: !"ab",
u: !undefined,
n: !null,
}}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
function foo() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (
<Stringify
value={{
_b: "baz",
b0: false,
n0: true,
n1: false,
n2: false,
n3: !-1,
s0: true,
s1: false,
s2: false,
u: !undefined,
n: true,
}}
/>
);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};
```
### Eval output
(kind: ok) <div>{"value":{"_b":"baz","b0":false,"n0":true,"n1":false,"n2":false,"n3":false,"s0":true,"s1":false,"s2":false,"u":true,"n":true}}</div>
@@ -0,0 +1,35 @@
import { Stringify } from "shared-runtime";
function foo() {
let _b;
const b = true;
if (!b) {
_b = "bar";
} else {
_b = "baz";
}
return (
<Stringify
value={{
_b,
b0: !true,
n0: !0,
n1: !1,
n2: !2,
n3: !-1,
s0: !"",
s1: !"a",
s2: !"ab",
u: !undefined,
n: !null,
}}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};
@@ -0,0 +1,27 @@
## Input
```javascript
function Component() {
const Foo = () => {
someGlobal = true;
};
return <Foo />;
}
```
## Error
```
1 | function Component() {
2 | const Foo = () => {
> 3 | someGlobal = true;
| ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3)
4 | };
5 | return <Foo />;
6 | }
```
@@ -0,0 +1,6 @@
function Component() {
const Foo = () => {
someGlobal = true;
};
return <Foo />;
}
@@ -0,0 +1,30 @@
## Input
```javascript
function Component() {
const foo = () => {
someGlobal = true;
};
// Children are generally access/called during render, so
// modifying a global in a children function is almost
// certainly a mistake.
return <Foo>{foo}</Foo>;
}
```
## Error
```
1 | function Component() {
2 | const foo = () => {
> 3 | someGlobal = true;
| ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3)
4 | };
5 | // Children are generally access/called during render, so
6 | // modifying a global in a children function is almost
```
@@ -0,0 +1,9 @@
function Component() {
const foo = () => {
someGlobal = true;
};
// Children are generally access/called during render, so
// modifying a global in a children function is almost
// certainly a mistake.
return <Foo>{foo}</Foo>;
}
@@ -0,0 +1,27 @@
## Input
```javascript
function Component() {
const foo = () => {
someGlobal = true;
};
return <div {...foo} />;
}
```
## Error
```
1 | function Component() {
2 | const foo = () => {
> 3 | someGlobal = true;
| ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3)
4 | };
5 | return <div {...foo} />;
6 | }
```
@@ -0,0 +1,6 @@
function Component() {
const foo = () => {
someGlobal = true;
};
return <div {...foo} />;
}
@@ -22,7 +22,7 @@ function Component(props) {
7 | return <Foo item={item} current={current} />;
8 | };
> 9 | return <Items>{props.items.map((item) => renderItem(item))}</Items>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $64[13:15]:TObject<BuiltInFunction> (9:9)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
10 | }
11 |
```
@@ -18,7 +18,7 @@ function Component(props) {
2 | function Component(props) {
3 | const ref = useRef(null);
> 4 | const x = foo(ref);
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $21[6:8]:TObject<BuiltInUseRefId> (4:4)
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
5 | return x.current;
6 | }
7 |
@@ -0,0 +1,24 @@
## Input
```javascript
function Component() {
const x = 0;
x = 1;
}
```
## Error
```
1 | function Component() {
2 | const x = 0;
> 3 | x = 1;
| ^ InvalidJS: Cannot reassign a `const` variable. `x` is declared as const (3:3)
4 | }
5 |
```
@@ -0,0 +1,4 @@
function Component() {
const x = 0;
x = 1;
}
@@ -21,7 +21,7 @@ function Component(props) {
6 | return <Foo item={item} current={current} />;
7 | };
> 8 | return <Items>{props.items.map((item) => renderItem(item))}</Items>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $60[14:16]:TObject<BuiltInFunction> (8:8)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (8:8)
9 | }
10 |
```
@@ -15,10 +15,13 @@ function Component(props) {
## Error
```
2 | function Component(props) {
3 | const ref = useRef(null);
4 | ref.current = props.value;
> 5 | return ref.current;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $24:TObject<BuiltInRefValue> (5:5)
> 4 | ref.current = props.value;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $24:TObject<BuiltInRefValue> (5:5)
5 | return ref.current;
6 | }
7 |
```
@@ -0,0 +1,29 @@
## Input
```javascript
// @validateRefAccessDuringRender
function Component(props) {
const ref = useRef({ inner: null });
ref.current.inner = props.value;
return ref.current.inner;
}
```
## Error
```
2 | function Component(props) {
3 | const ref = useRef({ inner: null });
> 4 | ref.current.inner = props.value;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $30:TObject<BuiltInRefValue> (5:5)
5 | return ref.current.inner;
6 | }
7 |
```
@@ -0,0 +1,6 @@
// @validateRefAccessDuringRender
function Component(props) {
const ref = useRef({ inner: null });
ref.current.inner = props.value;
return ref.current.inner;
}
@@ -25,7 +25,7 @@ function Foo({ a }) {
3 | const ref = useRef();
4 | // type information is lost here as we don't track types of fields
> 5 | const val = { ref };
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at capture $29:TObject<BuiltInUseRefId> (5:5)
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
6 | // without type info, we don't know that val.ref.current is a ref value so we
7 | // *would* end up depending on val.ref.current
8 | // however, this is an instance of accessing a ref during render and is disallowed
@@ -0,0 +1,29 @@
## Input
```javascript
// @validateRefAccessDuringRender
function useHook({ value }) {
const ref = useRef(null);
// Writing to a ref in render is against the rules:
ref.current = value;
// returning a ref is allowed, so this alone doesn't trigger an error:
return ref;
}
```
## Error
```
3 | const ref = useRef(null);
4 | // Writing to a ref in render is against the rules:
> 5 | ref.current = value;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
6 | // returning a ref is allowed, so this alone doesn't trigger an error:
7 | return ref;
8 | }
```
@@ -0,0 +1,8 @@
// @validateRefAccessDuringRender
function useHook({ value }) {
const ref = useRef(null);
// Writing to a ref in render is against the rules:
ref.current = value;
// returning a ref is allowed, so this alone doesn't trigger an error:
return ref;
}
@@ -1,32 +0,0 @@
## Input
```javascript
function Component() {
const foo = () => {
// Cannot assign to globals
someUnknownGlobal = true;
moduleLocal = true;
};
// It's possible that this could be an event handler / effect function,
// but we don't know that and conservatively assume it's a render helper
// where it's disallowed to modify globals
return <Foo foo={foo} />;
}
```
## Error
```
2 | const foo = () => {
3 | // Cannot assign to globals
> 4 | someUnknownGlobal = true;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (4:4)
5 | moduleLocal = true;
6 | };
7 | // It's possible that this could be an event handler / effect function,
```
@@ -1,11 +0,0 @@
function Component() {
const foo = () => {
// Cannot assign to globals
someUnknownGlobal = true;
moduleLocal = true;
};
// It's possible that this could be an event handler / effect function,
// but we don't know that and conservatively assume it's a render helper
// where it's disallowed to modify globals
return <Foo foo={foo} />;
}
@@ -0,0 +1,39 @@
## Input
```javascript
function Component({ value }) {
const object = {
get value() {
return value;
},
};
return <div>{object.value}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [{ value: 0 }],
sequentialRenders: [{ value: 1 }, { value: 2 }],
};
```
## Error
```
1 | function Component({ value }) {
2 | const object = {
> 3 | get value() {
| ^^^^^^^^^^^^^
> 4 | return value;
| ^^^^^^^^^^^^^^^^^^^
> 5 | },
| ^^^^^^ Todo: (BuildHIR::lowerExpression) Handle get functions in ObjectExpression (3:5)
6 | };
7 | return <div>{object.value}</div>;
8 | }
```
@@ -0,0 +1,14 @@
function Component({ value }) {
const object = {
get value() {
return value;
},
};
return <div>{object.value}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [{ value: 0 }],
sequentialRenders: [{ value: 1 }, { value: 2 }],
};
@@ -0,0 +1,41 @@
## Input
```javascript
function Component(props) {
let value;
const object = {
set value(v) {
value = v;
},
};
object.value = props.value;
return <div>{value}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [{ value: 0 }],
sequentialRenders: [{ value: 1 }, { value: 2 }],
};
```
## Error
```
2 | let value;
3 | const object = {
> 4 | set value(v) {
| ^^^^^^^^^^^^^^
> 5 | value = v;
| ^^^^^^^^^^^^^^^^
> 6 | },
| ^^^^^^ Todo: (BuildHIR::lowerExpression) Handle set functions in ObjectExpression (4:6)
7 | };
8 | object.value = props.value;
9 | return <div>{value}</div>;
```
@@ -0,0 +1,16 @@
function Component(props) {
let value;
const object = {
set value(v) {
value = v;
},
};
object.value = props.value;
return <div>{value}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [{ value: 0 }],
sequentialRenders: [{ value: 1 }, { value: 2 }],
};
@@ -23,7 +23,7 @@ export const FIXTURE_ENTRYPOINT = {
1 | // @validateRefAccessDuringRender:true
2 | function Foo(props, ref) {
> 3 | console.log(ref.current);
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at read $16:TObject<BuiltInRefValue> (3:3)
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
4 | return <div>{props.bar}</div>;
5 | }
6 |
@@ -0,0 +1,104 @@
## Input
```javascript
// @compilationMode(infer)
import { useEffect, useMemo, useState } from "react";
import { ValidateMemoization } from "shared-runtime";
let pretendConst = 0;
function unsafeResetConst() {
pretendConst = 0;
}
function unsafeUpdateConst() {
pretendConst += 1;
}
function Component() {
useState(() => {
// unsafe: reset the constant when first rendering the instance
unsafeResetConst();
});
// UNSAFE! changing a module variable that is read by a component is normally
// unsafe, but in this case we're simulating a fast refresh between each render
unsafeUpdateConst();
// In production mode (no @enableResetCacheOnSourceFileChanges) memo caches are not
// reset unless the deps change
const value = useMemo(() => [{ pretendConst }], []);
return <ValidateMemoization inputs={[]} output={value} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
sequentialRenders: [{}, {}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
import { useEffect, useMemo, useState } from "react";
import { ValidateMemoization } from "shared-runtime";
let pretendConst = 0;
function unsafeResetConst() {
pretendConst = 0;
}
function unsafeUpdateConst() {
pretendConst += 1;
}
function Component() {
const $ = _c(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
unsafeResetConst();
};
$[0] = t0;
} else {
t0 = $[0];
}
useState(t0);
unsafeUpdateConst();
let t1;
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [{ pretendConst }];
$[1] = t2;
} else {
t2 = $[1];
}
t1 = t2;
const value = t1;
let t3;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <ValidateMemoization inputs={[]} output={value} />;
$[2] = t3;
} else {
t3 = $[2];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
sequentialRenders: [{}, {}],
};
```
### Eval output
(kind: ok) <div>{"inputs":[],"output":[{"pretendConst":1}]}</div>
<div>{"inputs":[],"output":[{"pretendConst":1}]}</div>
@@ -0,0 +1,35 @@
// @compilationMode(infer)
import { useEffect, useMemo, useState } from "react";
import { ValidateMemoization } from "shared-runtime";
let pretendConst = 0;
function unsafeResetConst() {
pretendConst = 0;
}
function unsafeUpdateConst() {
pretendConst += 1;
}
function Component() {
useState(() => {
// unsafe: reset the constant when first rendering the instance
unsafeResetConst();
});
// UNSAFE! changing a module variable that is read by a component is normally
// unsafe, but in this case we're simulating a fast refresh between each render
unsafeUpdateConst();
// In production mode (no @enableResetCacheOnSourceFileChanges) memo caches are not
// reset unless the deps change
const value = useMemo(() => [{ pretendConst }], []);
return <ValidateMemoization inputs={[]} output={value} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
sequentialRenders: [{}, {}],
};
@@ -0,0 +1,112 @@
## Input
```javascript
// @compilationMode(infer) @enableResetCacheOnSourceFileChanges
import { useEffect, useMemo, useState } from "react";
import { ValidateMemoization } from "shared-runtime";
let pretendConst = 0;
function unsafeResetConst() {
pretendConst = 0;
}
function unsafeUpdateConst() {
pretendConst += 1;
}
function Component() {
useState(() => {
// unsafe: reset the constant when first rendering the instance
unsafeResetConst();
});
// UNSAFE! changing a module variable that is read by a component is normally
// unsafe, but in this case we're simulating a fast refresh between each render
unsafeUpdateConst();
// TODO: In fast refresh mode (@enableResetCacheOnSourceFileChanges) Forget should
// reset on changes to globals that impact the component/hook, effectively memoizing
// as if value was reactive. However, we don't want to actually treat globals as
// reactive (though that would be trivial) since it could change compilation too much
// btw dev and prod. Instead, we should reset the cache via a secondary mechanism.
const value = useMemo(() => [{ pretendConst }], [pretendConst]);
return <ValidateMemoization inputs={[pretendConst]} output={value} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
sequentialRenders: [{}, {}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableResetCacheOnSourceFileChanges
import { useEffect, useMemo, useState } from "react";
import { ValidateMemoization } from "shared-runtime";
let pretendConst = 0;
function unsafeResetConst() {
pretendConst = 0;
}
function unsafeUpdateConst() {
pretendConst += 1;
}
function Component() {
const $ = _c(4);
if (
$[0] !== "4bf230b116dd95f382060ad17350e116395e41ed757e51fd074ea0b4ed281272"
) {
for (let $i = 0; $i < 4; $i += 1) {
$[$i] = Symbol.for("react.memo_cache_sentinel");
}
$[0] = "4bf230b116dd95f382060ad17350e116395e41ed757e51fd074ea0b4ed281272";
}
let t0;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
unsafeResetConst();
};
$[1] = t0;
} else {
t0 = $[1];
}
useState(t0);
unsafeUpdateConst();
let t1;
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [{ pretendConst }];
$[2] = t2;
} else {
t2 = $[2];
}
t1 = t2;
const value = t1;
let t3;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <ValidateMemoization inputs={[pretendConst]} output={value} />;
$[3] = t3;
} else {
t3 = $[3];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
sequentialRenders: [{}, {}],
};
```
@@ -0,0 +1,38 @@
// @compilationMode(infer) @enableResetCacheOnSourceFileChanges
import { useEffect, useMemo, useState } from "react";
import { ValidateMemoization } from "shared-runtime";
let pretendConst = 0;
function unsafeResetConst() {
pretendConst = 0;
}
function unsafeUpdateConst() {
pretendConst += 1;
}
function Component() {
useState(() => {
// unsafe: reset the constant when first rendering the instance
unsafeResetConst();
});
// UNSAFE! changing a module variable that is read by a component is normally
// unsafe, but in this case we're simulating a fast refresh between each render
unsafeUpdateConst();
// TODO: In fast refresh mode (@enableResetCacheOnSourceFileChanges) Forget should
// reset on changes to globals that impact the component/hook, effectively memoizing
// as if value was reactive. However, we don't want to actually treat globals as
// reactive (though that would be trivial) since it could change compilation too much
// btw dev and prod. Instead, we should reset the cache via a secondary mechanism.
const value = useMemo(() => [{ pretendConst }], [pretendConst]);
return <ValidateMemoization inputs={[pretendConst]} output={value} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
sequentialRenders: [{}, {}],
};
@@ -40,7 +40,7 @@ function Component(props) {
title={fbs._(
"Hello {user name}",
[fbs._param("user name", props.name)],
{ hk: "2zEDKF" }
{ hk: "2zEDKF" },
)}
>
Hover me
@@ -34,7 +34,7 @@ function Component(props) {
t0 = fbt._(
"Hello, {(key) name}!",
[fbt._param("(key) name", identity(props.name))],
{ hk: "2sOsn5" }
{ hk: "2sOsn5" },
);
$[0] = props.name;
$[1] = t0;
@@ -32,7 +32,7 @@ function Component(props) {
t0 = fbt._(
"{(key) count} items",
[fbt._param("(key) count", props.count)],
{ hk: "3yW91j" }
{ hk: "3yW91j" },
);
$[0] = props.count;
$[1] = t0;
@@ -35,7 +35,7 @@ function Component(t0) {
t1 = fbt._(
"Before text{paramName}After text",
[fbt._param("paramName", value)],
{ hk: "aKEGX" }
{ hk: "aKEGX" },
);
$[0] = value;
$[1] = t1;
@@ -56,10 +56,10 @@ function Component(props) {
fbt._param(
"option",
props.option
props.option,
),
],
{ hk: "3Bg20a" }
{ hk: "3Bg20a" },
)}
!
</span>
@@ -56,10 +56,10 @@ function Component(props) {
fbt._param(
"option",
props.option
props.option,
),
],
{ hk: "3Bg20a" }
{ hk: "3Bg20a" },
)}
!
</span>
@@ -27,7 +27,7 @@ function Component(props) {
t0 = fbt._(
"Hello {user name}",
[fbt._param("user name", capitalize(props.name))],
{ hk: "2zEDKF" }
{ hk: "2zEDKF" },
);
$[0] = props.name;
$[1] = t0;
@@ -36,10 +36,10 @@ function Foo(props) {
fbt._param(
"value",
props.value
props.value,
),
],
{ hk: "Ri5kJ" }
{ hk: "Ri5kJ" },
);
$[0] = props.value;
$[1] = t0;
@@ -39,10 +39,10 @@ function Component(t0) {
fbt._param(
"paramName",
value
value,
),
],
{ hk: "3z5SVE" }
{ hk: "3z5SVE" },
);
$[0] = value;
$[1] = t1;
@@ -35,7 +35,7 @@ function Component(t0) {
t1 = fbt._(
"Before text {paramName} after text",
[fbt._param("paramName", value)],
{ hk: "26pxNm" }
{ hk: "26pxNm" },
);
$[0] = value;
$[1] = t1;

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