Skip to content

Commit 4ab06a7

Browse files
committed
[compiler] fix: resolve method signatures for unknown receiver types
When a method like `.map()` is called on a value with an unknown type (e.g., the return value of an unknown function), the compiler previously had no function signature available. This caused the fallback in `InferMutationAliasingEffects` to apply `MutateTransitiveConditionally` on the receiver, over-extending its mutable range and merging it into the same reactive scope as the method call result. This meant that `expensiveProcessing(data)` would be re-executed whenever `onClick` changed, even though it only depends on `data`. The fix adds a fallback in `getPropertyType`: when the receiver has an unresolved type (`Type` kind), try to resolve the property from the `BuiltInArray` shape — but only if the method also exists in the `BuiltInMixedReadonly` shape (the safe, non-mutating subset). This gives the compiler access to Array's aliasing signatures which correctly model data flow without over-extending mutable ranges. Fixes #35902
1 parent 4610359 commit 4ab06a7

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
makeScopeId,
4747
} from './HIR';
4848
import {
49+
BuiltInArrayId,
4950
BuiltInMixedReadonlyId,
5051
DefaultMutatingHook,
5152
DefaultNonmutatingHook,
@@ -1000,6 +1001,24 @@ export class Environment {
10001001
}
10011002
} else if (typeof property === 'string' && isHookName(property)) {
10021003
return this.#getCustomHookType();
1004+
} else if (receiver.kind === 'Type' && typeof property === 'string') {
1005+
/*
1006+
* For unknown receiver types, try to resolve from the BuiltInArray shape
1007+
* but only for methods also present in MixedReadonly (the safe subset).
1008+
* If .map() is called on an unknown type, the value must be array-like.
1009+
* We use the Array shape (not MixedReadonly) because it has aliasing
1010+
* signatures that correctly model data flow without over-extending
1011+
* mutable ranges. See ObjectShape.ts BuiltInMixedReadonlyId for details.
1012+
*/
1013+
const mixedShape = this.#shapes.get(BuiltInMixedReadonlyId);
1014+
const arrayShape = this.#shapes.get(BuiltInArrayId);
1015+
if (
1016+
mixedShape !== undefined &&
1017+
arrayShape !== undefined &&
1018+
mixedShape.properties.has(property)
1019+
) {
1020+
return arrayShape.properties.get(property) ?? null;
1021+
}
10031022
}
10041023
return null;
10051024
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
## Input
3+
4+
```javascript
5+
function ExpensiveComponent({data, onClick}) {
6+
const processedData = expensiveProcessing(data);
7+
return (
8+
<ul>
9+
{processedData.map((item) => (
10+
<li key={item.id} onClick={() => onClick(item)}>
11+
{item.name}
12+
</li>
13+
))}
14+
</ul>
15+
);
16+
}
17+
18+
```
19+
20+
## Code
21+
22+
```javascript
23+
import { c as _c } from "react/compiler-runtime";
24+
function ExpensiveComponent(t0) {
25+
const $ = _c(5);
26+
const {
27+
data,
28+
onClick
29+
} = t0;
30+
let t1;
31+
if ($[0] !== data) {
32+
t1 = expensiveProcessing(data);
33+
$[0] = data;
34+
$[1] = t1;
35+
} else {
36+
t1 = $[1];
37+
}
38+
const processedData = t1;
39+
let t2;
40+
if ($[2] !== onClick || $[3] !== processedData) {
41+
t2 = <ul>{processedData.map(item => <li key={item.id} onClick={() => onClick(item)}>{item.name}</li>)}</ul>;
42+
$[2] = onClick;
43+
$[3] = processedData;
44+
$[4] = t2;
45+
} else {
46+
t2 = $[4];
47+
}
48+
return t2;
49+
}
50+
51+
```
52+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
function ExpensiveComponent({data, onClick}) {
2+
const processedData = expensiveProcessing(data);
3+
return (
4+
<ul>
5+
{processedData.map((item) => (
6+
<li key={item.id} onClick={() => onClick(item)}>
7+
{item.name}
8+
</li>
9+
))}
10+
</ul>
11+
);
12+
}

0 commit comments

Comments
 (0)