mirror of
https://github.com/facebook/react.git
synced 2026-02-21 19:31:52 +00:00
[compiler] Fix bug w functions depending on hoisted primitives (#35284)
Fixes an edge case where a function expression would fail to take a dependency if it referenced a hoisted `const` inferred as a primitive value. We were incorrectly skipping primitve-typed operands when determing scopes for merging in InferReactiveScopeVariables. This was super tricky to debug, for posterity the trick is that Context variables (StoreContext etc) are modeled just like a mutable object, where assignment to the variable is equivalent to `object.value = ...` and reading the variable is equivalent to `object.value` property access. Comparing to an equivalent version of the repro case replaced with an object and property read/writes showed that everything was exactly right, except that InferReactiveScopeVariables wasn't merging the scopes of the function and the context variable, which led me right to the problematic line. Closes #35122
This commit is contained in:
@@ -389,14 +389,6 @@ export function findDisjointMutableValues(
|
||||
*/
|
||||
operand.identifier.mutableRange.start > 0
|
||||
) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
if (operand.identifier.type.kind === 'Primitive') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
operands.push(operand.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useState} from 'react';
|
||||
|
||||
/**
|
||||
* Repro for https://github.com/facebook/react/issues/35122
|
||||
*
|
||||
* InferReactiveScopeVariables was excluding primitive operands
|
||||
* when considering operands for merging. We previously did not
|
||||
* infer types for context variables (StoreContext etc), but later
|
||||
* started inferring types in cases of `const` context variables,
|
||||
* since the type cannot change.
|
||||
*
|
||||
* In this example, this meant that we skipped the `isExpired`
|
||||
* operand of the onClick function expression when considering
|
||||
* scopes to merge.
|
||||
*/
|
||||
function Test1() {
|
||||
const [expire, setExpire] = useState(5);
|
||||
|
||||
const onClick = () => {
|
||||
// Reference to isExpired prior to declaration
|
||||
console.log('isExpired', isExpired);
|
||||
};
|
||||
|
||||
const isExpired = expire === 0;
|
||||
|
||||
return <div onClick={onClick}>{expire}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test1,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useState } from "react";
|
||||
|
||||
/**
|
||||
* Repro for https://github.com/facebook/react/issues/35122
|
||||
*
|
||||
* InferReactiveScopeVariables was excluding primitive operands
|
||||
* when considering operands for merging. We previously did not
|
||||
* infer types for context variables (StoreContext etc), but later
|
||||
* started inferring types in cases of `const` context variables,
|
||||
* since the type cannot change.
|
||||
*
|
||||
* In this example, this meant that we skipped the `isExpired`
|
||||
* operand of the onClick function expression when considering
|
||||
* scopes to merge.
|
||||
*/
|
||||
function Test1() {
|
||||
const $ = _c(5);
|
||||
const [expire] = useState(5);
|
||||
let onClick;
|
||||
if ($[0] !== expire) {
|
||||
onClick = () => {
|
||||
console.log("isExpired", isExpired);
|
||||
};
|
||||
|
||||
const isExpired = expire === 0;
|
||||
$[0] = expire;
|
||||
$[1] = onClick;
|
||||
} else {
|
||||
onClick = $[1];
|
||||
}
|
||||
let t0;
|
||||
if ($[2] !== expire || $[3] !== onClick) {
|
||||
t0 = <div onClick={onClick}>{expire}</div>;
|
||||
$[2] = expire;
|
||||
$[3] = onClick;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
t0 = $[4];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test1,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>5</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
import {useState} from 'react';
|
||||
|
||||
/**
|
||||
* Repro for https://github.com/facebook/react/issues/35122
|
||||
*
|
||||
* InferReactiveScopeVariables was excluding primitive operands
|
||||
* when considering operands for merging. We previously did not
|
||||
* infer types for context variables (StoreContext etc), but later
|
||||
* started inferring types in cases of `const` context variables,
|
||||
* since the type cannot change.
|
||||
*
|
||||
* In this example, this meant that we skipped the `isExpired`
|
||||
* operand of the onClick function expression when considering
|
||||
* scopes to merge.
|
||||
*/
|
||||
function Test1() {
|
||||
const [expire, setExpire] = useState(5);
|
||||
|
||||
const onClick = () => {
|
||||
// Reference to isExpired prior to declaration
|
||||
console.log('isExpired', isExpired);
|
||||
};
|
||||
|
||||
const isExpired = expire === 0;
|
||||
|
||||
return <div onClick={onClick}>{expire}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test1,
|
||||
params: [{}],
|
||||
};
|
||||
Reference in New Issue
Block a user