diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 1c673bf2543a0..5cef2d28437b0 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -106,17 +106,19 @@ ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = } if (__DEV__) { - if (typeof arguments[1] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[1] === 'function') { console.error( 'does not support the second callback argument. ' + 'To execute a side effect after rendering, declare it in a component body with useEffect().', ); - } else if (isValidContainer(arguments[1])) { + } else if (isValidContainer(args[1])) { console.error( 'You passed a container to the second argument of root.render(...). ' + "You don't need to pass it again since you already passed it to create the root.", ); - } else if (typeof arguments[1] !== 'undefined') { + } else if (typeof args[1] !== 'undefined') { console.error( 'You passed a second argument to root.render(...) but it only accepts ' + 'one argument.', @@ -131,7 +133,9 @@ ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount = // $FlowFixMe[missing-this-annot] function (): void { if (__DEV__) { - if (typeof arguments[0] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[0] === 'function') { console.error( 'does not support a callback argument. ' + 'To execute a side effect after rendering, declare it in a component body with useEffect().', diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 12b110b16d3c9..99dbea044690f 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -3626,7 +3626,9 @@ function dispatchReducerAction( action: A, ): void { if (__DEV__) { - if (typeof arguments[3] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[3] === 'function') { console.error( "State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + @@ -3666,7 +3668,9 @@ function dispatchSetState( action: A, ): void { if (__DEV__) { - if (typeof arguments[3] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[3] === 'function') { console.error( "State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + diff --git a/packages/react/src/__tests__/React-hooks-arity.js b/packages/react/src/__tests__/React-hooks-arity.js new file mode 100644 index 0000000000000..0ba63428d3989 --- /dev/null +++ b/packages/react/src/__tests__/React-hooks-arity.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactNoop; + +describe('arity', () => { + beforeEach(() => { + jest.resetModules(); + + React = require('react'); + ReactNoop = require('react-noop-renderer'); + }); + + it("ensure useState setter's arity is correct", () => { + function Component() { + const [, setState] = React.useState(() => 'Halo!'); + + expect(setState.length).toBe(1); + return null; + } + + ReactNoop.render(); + }); + + it("ensure useReducer setter's arity is correct", () => { + function Component() { + const [, dispatch] = React.useReducer(() => 'Halo!'); + + expect(dispatch.length).toBe(1); + return null; + } + + ReactNoop.render(); + }); +}); diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index c91c298edf1be..b974ecee0d903 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -86,6 +86,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index 01f387e1d7188..6efc8838f0326 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -81,6 +81,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.esm.js b/scripts/rollup/validate/eslintrc.esm.js index 9f9938f204d21..20b5341a82ad8 100644 --- a/scripts/rollup/validate/eslintrc.esm.js +++ b/scripts/rollup/validate/eslintrc.esm.js @@ -83,6 +83,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js index 9f344c5aac3aa..9606d00b353ac 100644 --- a/scripts/rollup/validate/eslintrc.fb.js +++ b/scripts/rollup/validate/eslintrc.fb.js @@ -71,6 +71,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js index d18516f802304..941b1d1872efd 100644 --- a/scripts/rollup/validate/eslintrc.rn.js +++ b/scripts/rollup/validate/eslintrc.rn.js @@ -73,6 +73,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment