Skip to content

Commit eb53139

Browse files
authored
[compiler][optim] infer mixedReadOnly for numeric and computed properties (#32593)
Expand type inference to infer mixedReadOnly types for numeric and computed property accesses. ```js function Component({idx}) const data = useFragment(...) // we want to type `posts` correctly as Array const posts = data.viewers[idx].posts.slice(0, 5); // ... } ``` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32593). * #32596 * #32595 * #32594 * __->__ #32593 * #32522 * #32521
1 parent 38a7600 commit eb53139

File tree

5 files changed

+161
-21
lines changed

5 files changed

+161
-21
lines changed

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

+34-9
Original file line numberDiff line numberDiff line change
@@ -1126,9 +1126,32 @@ export class Environment {
11261126
);
11271127
}
11281128

1129+
getFallthroughPropertyType(
1130+
receiver: Type,
1131+
_property: Type,
1132+
): BuiltInType | PolyType | null {
1133+
let shapeId = null;
1134+
if (receiver.kind === 'Object' || receiver.kind === 'Function') {
1135+
shapeId = receiver.shapeId;
1136+
}
1137+
1138+
if (shapeId !== null) {
1139+
const shape = this.#shapes.get(shapeId);
1140+
1141+
CompilerError.invariant(shape !== undefined, {
1142+
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
1143+
description: null,
1144+
loc: null,
1145+
suggestions: null,
1146+
});
1147+
return shape.properties.get('*') ?? null;
1148+
}
1149+
return null;
1150+
}
1151+
11291152
getPropertyType(
11301153
receiver: Type,
1131-
property: string,
1154+
property: string | number,
11321155
): BuiltInType | PolyType | null {
11331156
let shapeId = null;
11341157
if (receiver.kind === 'Object' || receiver.kind === 'Function') {
@@ -1146,17 +1169,19 @@ export class Environment {
11461169
loc: null,
11471170
suggestions: null,
11481171
});
1149-
let value =
1150-
shape.properties.get(property) ?? shape.properties.get('*') ?? null;
1151-
if (value === null && isHookName(property)) {
1152-
value = this.#getCustomHookType();
1172+
if (typeof property === 'string') {
1173+
return (
1174+
shape.properties.get(property) ??
1175+
shape.properties.get('*') ??
1176+
(isHookName(property) ? this.#getCustomHookType() : null)
1177+
);
1178+
} else {
1179+
return shape.properties.get('*') ?? null;
11531180
}
1154-
return value;
1155-
} else if (isHookName(property)) {
1181+
} else if (typeof property === 'string' && isHookName(property)) {
11561182
return this.#getCustomHookType();
1157-
} else {
1158-
return null;
11591183
}
1184+
return null;
11601185
}
11611186

11621187
getFunctionSignature(type: FunctionType): FunctionSignature | null {

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,15 @@ export type PropType = {
6060
kind: 'Property';
6161
objectType: Type;
6262
objectName: string;
63-
propertyName: PropertyLiteral;
63+
propertyName:
64+
| {
65+
kind: 'literal';
66+
value: PropertyLiteral;
67+
}
68+
| {
69+
kind: 'computed';
70+
value: Type;
71+
};
6472
};
6573

6674
export type ObjectMethod = {

compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts

+36-11
Original file line numberDiff line numberDiff line change
@@ -307,11 +307,26 @@ function* generateInstructionTypes(
307307
kind: 'Property',
308308
objectType: value.object.identifier.type,
309309
objectName: getName(names, value.object.identifier.id),
310-
propertyName: value.property,
310+
propertyName: {
311+
kind: 'literal',
312+
value: value.property,
313+
},
311314
});
312315
break;
313316
}
314317

318+
case 'ComputedLoad': {
319+
yield equation(left, {
320+
kind: 'Property',
321+
objectType: value.object.identifier.type,
322+
objectName: getName(names, value.object.identifier.id),
323+
propertyName: {
324+
kind: 'computed',
325+
value: value.property.identifier.type,
326+
},
327+
});
328+
break;
329+
}
315330
case 'MethodCall': {
316331
const returnType = makeType();
317332
yield equation(value.property.identifier.type, {
@@ -336,7 +351,10 @@ function* generateInstructionTypes(
336351
kind: 'Property',
337352
objectType: value.value.identifier.type,
338353
objectName: getName(names, value.value.identifier.id),
339-
propertyName: makePropertyLiteral(propertyName),
354+
propertyName: {
355+
kind: 'literal',
356+
value: makePropertyLiteral(propertyName),
357+
},
340358
});
341359
} else {
342360
break;
@@ -353,7 +371,10 @@ function* generateInstructionTypes(
353371
kind: 'Property',
354372
objectType: value.value.identifier.type,
355373
objectName: getName(names, value.value.identifier.id),
356-
propertyName: makePropertyLiteral(property.key.name),
374+
propertyName: {
375+
kind: 'literal',
376+
value: makePropertyLiteral(property.key.name),
377+
},
357378
});
358379
}
359380
}
@@ -410,7 +431,6 @@ function* generateInstructionTypes(
410431
case 'RegExpLiteral':
411432
case 'MetaProperty':
412433
case 'ComputedStore':
413-
case 'ComputedLoad':
414434
case 'Await':
415435
case 'GetIterator':
416436
case 'IteratorNext':
@@ -454,12 +474,13 @@ class Unifier {
454474
return;
455475
}
456476
const objectType = this.get(tB.objectType);
457-
let propertyType;
458-
if (typeof tB.propertyName === 'number') {
459-
propertyType = null;
460-
} else {
461-
propertyType = this.env.getPropertyType(objectType, tB.propertyName);
462-
}
477+
const propertyType =
478+
tB.propertyName.kind === 'literal'
479+
? this.env.getPropertyType(objectType, tB.propertyName.value)
480+
: this.env.getFallthroughPropertyType(
481+
objectType,
482+
tB.propertyName.value,
483+
);
463484
if (propertyType !== null) {
464485
this.unify(tA, propertyType);
465486
}
@@ -677,7 +698,11 @@ class Unifier {
677698
const RefLikeNameRE = /^(?:[a-zA-Z$_][a-zA-Z$_0-9]*)Ref$|^ref$/;
678699

679700
function isRefLikeName(t: PropType): boolean {
680-
return RefLikeNameRE.test(t.objectName) && t.propertyName === 'current';
701+
return (
702+
t.propertyName.kind === 'literal' &&
703+
RefLikeNameRE.test(t.objectName) &&
704+
t.propertyName.value === 'current'
705+
);
681706
}
682707

683708
function tryUnionTypes(ty1: Type, ty2: Type): Type | null {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
2+
## Input
3+
4+
```javascript
5+
import {useFragment} from 'shared-runtime';
6+
7+
/**
8+
* React compiler should infer that the returned value is a primitive and avoid
9+
* memoizing it.
10+
*/
11+
function useRelayData({query, idx}) {
12+
'use memo';
13+
const data = useFragment('', query);
14+
return data.a[idx].toString();
15+
}
16+
17+
export const FIXTURE_ENTRYPOINT = {
18+
fn: useRelayData,
19+
params: [{query: '', idx: 0}],
20+
sequentialRenders: [
21+
{query: '', idx: 0},
22+
{query: '', idx: 0},
23+
{query: '', idx: 1},
24+
],
25+
};
26+
27+
```
28+
29+
## Code
30+
31+
```javascript
32+
import { useFragment } from "shared-runtime";
33+
34+
/**
35+
* React compiler should infer that the returned value is a primitive and avoid
36+
* memoizing it.
37+
*/
38+
function useRelayData(t0) {
39+
"use memo";
40+
const { query, idx } = t0;
41+
42+
const data = useFragment("", query);
43+
return data.a[idx].toString();
44+
}
45+
46+
export const FIXTURE_ENTRYPOINT = {
47+
fn: useRelayData,
48+
params: [{ query: "", idx: 0 }],
49+
sequentialRenders: [
50+
{ query: "", idx: 0 },
51+
{ query: "", idx: 0 },
52+
{ query: "", idx: 1 },
53+
],
54+
};
55+
56+
```
57+
58+
### Eval output
59+
(kind: ok) "1"
60+
"1"
61+
"2"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {useFragment} from 'shared-runtime';
2+
3+
/**
4+
* React compiler should infer that the returned value is a primitive and avoid
5+
* memoizing it.
6+
*/
7+
function useRelayData({query, idx}) {
8+
'use memo';
9+
const data = useFragment('', query);
10+
return data.a[idx].toString();
11+
}
12+
13+
export const FIXTURE_ENTRYPOINT = {
14+
fn: useRelayData,
15+
params: [{query: '', idx: 0}],
16+
sequentialRenders: [
17+
{query: '', idx: 0},
18+
{query: '', idx: 0},
19+
{query: '', idx: 1},
20+
],
21+
};

0 commit comments

Comments
 (0)