@samitouri / QOS-React / commits / a9575dcf62

[compiler] Represent array accesses with PropertyLoad (#32287)

Prior to this PR, our HIR represented property access with numeric literals (e.g. `myVar[0]`) as ComputedLoads. This means that they were subject to some deopts (most notably, not being easily dedupable / hoistable as dependencies). Now, `PropertyLoad`, `PropertyStore`, etc reference numeric and string literals (although not yet string literals that aren't valid babel identifiers). The difference between PropertyLoad and ComputedLoad is fuzzy now (maybe we should rename these). - PropertyLoad: property keys are string and numeric literals, only when the string literals are valid babel identifiers - ComputedLoad: non-valid babel identifier string literals (rare) and other non-literal expressions The biggest feature from this PR is that it trivially enables array-indicing expressions as dependencies. The compiler can also specify global and imported types for arrays (e.g. return value of `useState`) I'm happy to close this if it complicates more than it helps -- alternative options are to entirely rely on instruction reordering-based approaches like ReactiveGraphIR or make dependency-specific parsing + hoisting logic more robust.

mofeiZ committed Feb 18, 2025 at 11:54 UTC a9575dcf62e5cb6f8b1d8f738aa75ece216d9054
26 files changed +322 -253
compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
+38 -21
@@ -36,12 +36,14 @@ import {
36 ObjectProperty,
37 ObjectPropertyKey,
38 Place,
39 + PropertyLiteral,
40 ReturnTerminal,
41 SourceLocation,
42 SpreadPattern,
43 ThrowTerminal,
44 Type,
45 makeInstructionId,
46 + makePropertyLiteral,
47 makeType,
48 promoteTemporary,
49 } from './HIR';
@@ -2017,11 +2019,11 @@ function lowerExpression(
2019 });
2020
2021 // Save the result back to the property
2020 - if (typeof property === 'string') {
2022 + if (typeof property === 'string' || typeof property === 'number') {
2023 return {
2024 kind: 'PropertyStore',
2025 object: {...object},
2024 - property,
2026 + property: makePropertyLiteral(property),
2027 value: {...newValuePlace},
2028 loc: leftExpr.node.loc ?? GeneratedSource,
2029 };
@@ -2316,11 +2318,11 @@ function lowerExpression(
2318 const argument = expr.get('argument');
2319 if (argument.isMemberExpression()) {
2320 const {object, property} = lowerMemberExpression(builder, argument);
2319 - if (typeof property === 'string') {
2321 + if (typeof property === 'string' || typeof property === 'number') {
2322 return {
2323 kind: 'PropertyDelete',
2324 object,
2323 - property,
2325 + property: makePropertyLiteral(property),
2326 loc: exprLoc,
2327 };
2328 } else {
@@ -2427,11 +2429,11 @@ function lowerExpression(
2429
2430 // Save the result back to the property
2431 let newValuePlace;
2430 - if (typeof property === 'string') {
2432 + if (typeof property === 'string' || typeof property === 'number') {
2433 newValuePlace = lowerValueToTemporary(builder, {
2434 kind: 'PropertyStore',
2435 object: {...object},
2434 - property,
2436 + property: makePropertyLiteral(property),
2437 value: {...updatedValue},
2438 loc: leftExpr.node.loc ?? GeneratedSource,
2439 });
@@ -3057,7 +3059,7 @@ function lowerArguments(
3059
3060 type LoweredMemberExpression = {
3061 object: Place;
3060 - property: Place | string;
3062 + property: Place | string | number;
3063 value: InstructionValue;
3064 };
3065 function lowerMemberExpression(
@@ -3072,8 +3074,13 @@ function lowerMemberExpression(
3074 const object =
3075 loweredObject ?? lowerExpressionToTemporary(builder, objectNode);
3076
3075 - if (!expr.node.computed) {
3076 - if (!propertyNode.isIdentifier()) {
3077 + if (!expr.node.computed || expr.node.property.type === 'NumericLiteral') {
3078 + let property: PropertyLiteral;
3079 + if (propertyNode.isIdentifier()) {
3080 + property = makePropertyLiteral(propertyNode.node.name);
3081 + } else if (propertyNode.isNumericLiteral()) {
3082 + property = makePropertyLiteral(propertyNode.node.value);
3083 + } else {
3084 builder.errors.push({
3085 reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`,
3086 severity: ErrorSeverity.Todo,
@@ -3089,10 +3096,10 @@ function lowerMemberExpression(
3096 const value: InstructionValue = {
3097 kind: 'PropertyLoad',
3098 object: {...object},
3092 - property: propertyNode.node.name,
3099 + property,
3100 loc: exprLoc,
3101 };
3095 - return {object, property: propertyNode.node.name, value};
3102 + return {object, property, value};
3103 } else {
3104 if (!propertyNode.isExpression()) {
3105 builder.errors.push({
@@ -3210,7 +3217,7 @@ function lowerJsxMemberExpression(
3217 return lowerValueToTemporary(builder, {
3218 kind: 'PropertyLoad',
3219 object: objectPlace,
3213 - property,
3220 + property: makePropertyLiteral(property),
3221 loc,
3222 });
3223 }
@@ -3626,8 +3633,25 @@ function lowerAssignment(
3633 const lvalue = lvaluePath as NodePath<t.MemberExpression>;
3634 const property = lvalue.get('property');
3635 const object = lowerExpressionToTemporary(builder, lvalue.get('object'));
3629 - if (!lvalue.node.computed) {
3630 - if (!property.isIdentifier()) {
3636 + if (!lvalue.node.computed || lvalue.get('property').isNumericLiteral()) {
3637 + let temporary;
3638 + if (property.isIdentifier()) {
3639 + temporary = lowerValueToTemporary(builder, {
3640 + kind: 'PropertyStore',
3641 + object,
3642 + property: makePropertyLiteral(property.node.name),
3643 + value,
3644 + loc,
3645 + });
3646 + } else if (property.isNumericLiteral()) {
3647 + temporary = lowerValueToTemporary(builder, {
3648 + kind: 'PropertyStore',
3649 + object,
3650 + property: makePropertyLiteral(property.node.value),
3651 + value,
3652 + loc,
3653 + });
3654 + } else {
3655 builder.errors.push({
3656 reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`,
3657 severity: ErrorSeverity.Todo,
@@ -3636,13 +3660,6 @@ function lowerAssignment(
3660 });
3661 return {kind: 'UnsupportedNode', node: lvalueNode, loc};
3662 }
3639 - const temporary = lowerValueToTemporary(builder, {
3640 - kind: 'PropertyStore',
3641 - object,
3642 - property: property.node.name,
3643 - value,
3644 - loc,
3645 - });
3663 return {kind: 'LoadLocal', place: temporary, loc: temporary.loc};
3664 } else {
3665 if (!property.isExpression()) {
compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts
+5 -4
@@ -18,6 +18,7 @@ import {
18 IdentifierId,
19 InstructionId,
20 InstructionValue,
21 + PropertyLiteral,
22 ReactiveScopeDependency,
23 ScopeId,
24 } from './HIR';
@@ -172,8 +173,8 @@ export type BlockInfo = {
173 * and make computing sets intersections simpler.
174 */
175 type RootNode = {
175 - properties: Map<string, PropertyPathNode>;
176 - optionalProperties: Map<string, PropertyPathNode>;
176 + properties: Map<PropertyLiteral, PropertyPathNode>;
177 + optionalProperties: Map<PropertyLiteral, PropertyPathNode>;
178 parent: null;
179 // Recorded to make later computations simpler
180 fullPath: ReactiveScopeDependency;
@@ -183,8 +184,8 @@ type RootNode = {
184
185 type PropertyPathNode =
186 | {
186 - properties: Map<string, PropertyPathNode>;
187 - optionalProperties: Map<string, PropertyPathNode>;
187 + properties: Map<PropertyLiteral, PropertyPathNode>;
188 + optionalProperties: Map<PropertyLiteral, PropertyPathNode>;
189 parent: PropertyPathNode;
190 fullPath: ReactiveScopeDependency;
191 hasOptional: boolean;
compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts
+2 -1
@@ -16,6 +16,7 @@ import {
16 DependencyPathEntry,
17 Instruction,
18 Terminal,
19 + PropertyLiteral,
20 } from './HIR';
21 import {printIdentifier} from './PrintHIR';
22
@@ -157,7 +158,7 @@ function matchOptionalTestBlock(
158 blocks: ReadonlyMap<BlockId, BasicBlock>,
159 ): {
160 consequentId: IdentifierId;
160 - property: string;
161 + property: PropertyLiteral;
162 propertyId: IdentifierId;
163 storeLocalInstr: Instruction;
164 consequentGoto: BlockId;
compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts
+3 -2
@@ -10,6 +10,7 @@ import {
10 DependencyPathEntry,
11 GeneratedSource,
12 Identifier,
13 + PropertyLiteral,
14 ReactiveScopeDependency,
15 } from '../HIR';
16 import {printIdentifier} from '../HIR/PrintHIR';
@@ -286,7 +287,7 @@ function merge(
287 }
288
289 type TreeNode<T extends string> = {
289 - properties: Map<string, TreeNode<T>>;
290 + properties: Map<PropertyLiteral, TreeNode<T>>;
291 accessType: T;
292 };
293 type HoistableNode = TreeNode<'Optional' | 'NonNull'>;
@@ -343,7 +344,7 @@ function printSubtree(
344
345 function makeOrMergeProperty(
346 node: DependencyNode,
346 - property: string,
347 + property: PropertyLiteral,
348 accessType: PropertyAccessType,
349 ): DependencyNode {
350 let child = node.properties.get(property);
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+14 -4
@@ -937,7 +937,7 @@ export type InstructionValue =
937 | {
938 kind: 'PropertyStore';
939 object: Place;
940 - property: string;
940 + property: PropertyLiteral;
941 value: Place;
942 loc: SourceLocation;
943 }
@@ -947,7 +947,7 @@ export type InstructionValue =
947 | {
948 kind: 'PropertyDelete';
949 object: Place;
950 - property: string;
950 + property: PropertyLiteral;
951 loc: SourceLocation;
952 }
953
@@ -1121,7 +1121,7 @@ export type StoreLocal = {
1121 export type PropertyLoad = {
1122 kind: 'PropertyLoad';
1123 object: Place;
1124 - property: string;
1124 + property: PropertyLiteral;
1125 loc: SourceLocation;
1126 };
1127
@@ -1502,7 +1502,17 @@ export type ReactiveScopeDeclaration = {
1502 scope: ReactiveScope; // the scope in which the variable was originally declared
1503 };
1504
1505 -export type DependencyPathEntry = {property: string; optional: boolean};
1505 +const opaquePropertyLiteral = Symbol();
1506 +export type PropertyLiteral = (string | number) & {
1507 + [opaquePropertyLiteral]: 'PropertyLiteral';
1508 +};
1509 +export function makePropertyLiteral(value: string | number): PropertyLiteral {
1510 + return value as PropertyLiteral;
1511 +}
1512 +export type DependencyPathEntry = {
1513 + property: PropertyLiteral;
1514 + optional: boolean;
1515 +};
1516 export type DependencyPath = Array<DependencyPathEntry>;
1517 export type ReactiveScopeDependency = {
1518 identifier: Identifier;
compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts
+7 -2
@@ -22,6 +22,7 @@ import {
22 TInstruction,
23 FunctionExpression,
24 ObjectMethod,
25 + PropertyLiteral,
26 } from './HIR';
27 import {
28 collectHoistablePropertyLoads,
@@ -321,7 +322,7 @@ function collectTemporariesSidemapImpl(
322
323 function getProperty(
324 object: Place,
324 - propertyName: string,
325 + propertyName: PropertyLiteral,
326 optional: boolean,
327 temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
328 ): ReactiveScopeDependency {
@@ -519,7 +520,11 @@ class Context {
520 );
521 }
522
522 - visitProperty(object: Place, property: string, optional: boolean): void {
523 + visitProperty(
524 + object: Place,
525 + property: PropertyLiteral,
526 + optional: boolean,
527 + ): void {
528 const nextDependency = getProperty(
529 object,
530 property,
compiler/packages/babel-plugin-react-compiler/src/HIR/Types.ts
+2 -1
@@ -6,6 +6,7 @@
6 */
7
8 import {CompilerError} from '../CompilerError';
9 +import {PropertyLiteral} from './HIR';
10
11 export type BuiltInType = PrimitiveType | FunctionType | ObjectType;
12
@@ -59,7 +60,7 @@ export type PropType = {
60 kind: 'Property';
61 objectType: Type;
62 objectName: string;
62 - propertyName: string;
63 + propertyName: PropertyLiteral;
64 };
65
66 export type ObjectMethod = {
compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts
+3 -2
@@ -145,9 +145,10 @@ function collectTemporaries(
145 }
146 case 'PropertyLoad': {
147 if (sidemap.react.has(value.object.identifier.id)) {
148 - if (value.property === 'useMemo' || value.property === 'useCallback') {
148 + const property = value.property;
149 + if (property === 'useMemo' || property === 'useCallback') {
150 sidemap.manualMemos.set(instr.lvalue.identifier.id, {
150 - kind: value.property,
151 + kind: property as 'useMemo' | 'useCallback',
152 loadInstr: instr as TInstruction<PropertyLoad>,
153 });
154 }
compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts
+9 -6
@@ -19,6 +19,7 @@ import {
19 Primitive,
20 assertConsistentIdentifiers,
21 assertTerminalSuccessorsExist,
22 + makePropertyLiteral,
23 markInstructionIds,
24 markPredecessors,
25 mergeConsecutiveBlocks,
@@ -238,13 +239,14 @@ function evaluateInstruction(
239 if (
240 property !== null &&
241 property.kind === 'Primitive' &&
241 - typeof property.value === 'string' &&
242 - isValidIdentifier(property.value)
242 + ((typeof property.value === 'string' &&
243 + isValidIdentifier(property.value)) ||
244 + typeof property.value === 'number')
245 ) {
246 const nextValue: InstructionValue = {
247 kind: 'PropertyLoad',
248 loc: value.loc,
247 - property: property.value,
249 + property: makePropertyLiteral(property.value),
250 object: value.object,
251 };
252 instr.value = nextValue;
@@ -256,13 +258,14 @@ function evaluateInstruction(
258 if (
259 property !== null &&
260 property.kind === 'Primitive' &&
259 - typeof property.value === 'string' &&
260 - isValidIdentifier(property.value)
261 + ((typeof property.value === 'string' &&
262 + isValidIdentifier(property.value)) ||
263 + typeof property.value === 'number')
264 ) {
265 const nextValue: InstructionValue = {
266 kind: 'PropertyStore',
267 loc: value.loc,
265 - property: property.value,
268 + property: makePropertyLiteral(property.value),
269 object: value.object,
270 value: value.value,
271 };
compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts
+2 -1
@@ -21,6 +21,7 @@ import {
21 InstructionKind,
22 JsxAttribute,
23 makeInstructionId,
24 + makePropertyLiteral,
25 ObjectProperty,
26 Phi,
27 Place,
@@ -446,7 +447,7 @@ function createSymbolProperty(
447 value: {
448 kind: 'PropertyLoad',
449 object: {...symbolInstruction.lvalue},
449 - property: 'for',
450 + property: makePropertyLiteral('for'),
451 loc: instr.value.loc,
452 },
453 loc: instr.loc,
compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts
+2 -1
@@ -23,6 +23,7 @@ import {
23 isUseContextHookType,
24 makeBlockId,
25 makeInstructionId,
26 + makePropertyLiteral,
27 makeType,
28 markInstructionIds,
29 promoteTemporary,
@@ -195,7 +196,7 @@ function emitPropertyLoad(
196 const loadProp: PropertyLoad = {
197 kind: 'PropertyLoad',
198 object,
198 - property,
199 + property: makePropertyLiteral(property),
200 loc: GeneratedSource,
201 };
202 const element: Place = createTemporaryPlace(env, GeneratedSource);
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
+33 -29
@@ -1453,15 +1453,20 @@ function codegenDependency(
1453 if (dependency.path.length !== 0) {
1454 const hasOptional = dependency.path.some(path => path.optional);
1455 for (const path of dependency.path) {
1456 + const property =
1457 + typeof path.property === 'string'
1458 + ? t.identifier(path.property)
1459 + : t.numericLiteral(path.property);
1460 + const isComputed = typeof path.property !== 'string';
1461 if (hasOptional) {
1462 object = t.optionalMemberExpression(
1463 object,
1459 - t.identifier(path.property),
1460 - false,
1464 + property,
1465 + isComputed,
1466 path.optional,
1467 );
1468 } else {
1464 - object = t.memberExpression(object, t.identifier(path.property));
1469 + object = t.memberExpression(object, property, isComputed);
1470 }
1471 }
1472 }
@@ -1962,38 +1967,37 @@ function codegenInstructionValue(
1967 value = node;
1968 break;
1969 }
1965 - case 'PropertyStore': {
1966 - value = t.assignmentExpression(
1967 - '=',
1968 - t.memberExpression(
1969 - codegenPlaceToExpression(cx, instrValue.object),
1970 - t.identifier(instrValue.property),
1971 - ),
1972 - codegenPlaceToExpression(cx, instrValue.value),
1973 - );
1974 - break;
1975 - }
1976 - case 'PropertyLoad': {
1977 - const object = codegenPlaceToExpression(cx, instrValue.object);
1970 + case 'PropertyStore':
1971 + case 'PropertyLoad':
1972 + case 'PropertyDelete': {
1973 + let memberExpr;
1974 /*
1975 * We currently only lower single chains of optional memberexpr.
1976 * (See BuildHIR.ts for more detail.)
1977 */
1982 - value = t.memberExpression(
1983 - object,
1984 - t.identifier(instrValue.property),
1985 - undefined,
1986 - );
1987 - break;
1988 - }
1989 - case 'PropertyDelete': {
1990 - value = t.unaryExpression(
1991 - 'delete',
1992 - t.memberExpression(
1978 + if (typeof instrValue.property === 'string') {
1979 + memberExpr = t.memberExpression(
1980 codegenPlaceToExpression(cx, instrValue.object),
1981 t.identifier(instrValue.property),
1995 - ),
1996 - );
1982 + );
1983 + } else {
1984 + memberExpr = t.memberExpression(
1985 + codegenPlaceToExpression(cx, instrValue.object),
1986 + t.numericLiteral(instrValue.property),
1987 + true,
1988 + );
1989 + }
1990 + if (instrValue.kind === 'PropertyStore') {
1991 + value = t.assignmentExpression(
1992 + '=',
1993 + memberExpr,
1994 + codegenPlaceToExpression(cx, instrValue.value),
1995 + );
1996 + } else if (instrValue.kind === 'PropertyLoad') {
1997 + value = memberExpr;
1998 + } else {
1999 + value = t.unaryExpression('delete', memberExpr);
2000 + }
2001 break;
2002 }
2003 case 'ComputedStore': {
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts
+2 -1
@@ -17,6 +17,7 @@ import {
17 ReactiveStatement,
18 ReactiveTerminalStatement,
19 makeInstructionId,
20 + makePropertyLiteral,
21 promoteTemporary,
22 } from '../HIR';
23 import {createTemporaryPlace} from '../HIR/HIRBuilder';
@@ -189,7 +190,7 @@ class Transform extends ReactiveFunctionTransform<State> {
190 value: {
191 kind: 'PropertyLoad',
192 object: {...symbolTemp},
192 - property: 'for',
193 + property: makePropertyLiteral('for'),
194 loc,
195 },
196 },
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts
+11 -7
@@ -12,6 +12,7 @@ import {
12 IdentifierId,
13 InstructionId,
14 Place,
15 + PropertyLiteral,
16 ReactiveBlock,
17 ReactiveFunction,
18 ReactiveInstruction,
@@ -64,13 +65,13 @@ type KindMap = Map<IdentifierId, CreateUpdate>;
65 class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
66 map: KindMap = new Map();
67 aliases: DisjointSet<IdentifierId>;
67 - paths: Map<IdentifierId, Map<string, IdentifierId>>;
68 + paths: Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>;
69 env: Environment;
70
71 constructor(
72 env: Environment,
73 aliases: DisjointSet<IdentifierId>,
73 - paths: Map<IdentifierId, Map<string, IdentifierId>>,
74 + paths: Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>,
75 ) {
76 super();
77 this.aliases = aliases;
@@ -218,9 +219,9 @@ export default function pruneInitializationDependencies(
219 }
220
221 function update(
221 - map: Map<IdentifierId, Map<string, IdentifierId>>,
222 + map: Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>,
223 key: IdentifierId,
223 - path: string,
224 + path: PropertyLiteral,
225 value: IdentifierId,
226 ): void {
227 const inner = map.get(key) ?? new Map();
@@ -230,7 +231,7 @@ function update(
231
232 class AliasVisitor extends ReactiveFunctionVisitor {
233 scopeIdentifiers: DisjointSet<IdentifierId> = new DisjointSet<IdentifierId>();
233 - scopePaths: Map<IdentifierId, Map<string, IdentifierId>> = new Map();
234 + scopePaths: Map<IdentifierId, Map<PropertyLiteral, IdentifierId>> = new Map();
235
236 override visitInstruction(instr: ReactiveInstruction): void {
237 if (
@@ -271,11 +272,14 @@ class AliasVisitor extends ReactiveFunctionVisitor {
272
273 function getAliases(
274 fn: ReactiveFunction,
274 -): [DisjointSet<IdentifierId>, Map<IdentifierId, Map<string, IdentifierId>>] {
275 +): [
276 + DisjointSet<IdentifierId>,
277 + Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>,
278 +] {
279 const visitor = new AliasVisitor();
280 visitReactiveFunction(fn, visitor, null);
281 let disjoint = visitor.scopeIdentifiers;
278 - let scopePaths = new Map<IdentifierId, Map<string, IdentifierId>>();
282 + let scopePaths = new Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>();
283 for (const [key, value] of visitor.scopePaths) {
284 for (const [path, id] of value) {
285 update(
compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts
+9 -6
@@ -14,6 +14,7 @@ import {
14 Identifier,
15 IdentifierId,
16 Instruction,
17 + makePropertyLiteral,
18 makeType,
19 PropType,
20 Type,
@@ -335,7 +336,7 @@ function* generateInstructionTypes(
336 kind: 'Property',
337 objectType: value.value.identifier.type,
338 objectName: getName(names, value.value.identifier.id),
338 - propertyName,
339 + propertyName: makePropertyLiteral(propertyName),
340 });
341 } else {
342 break;
@@ -352,7 +353,7 @@ function* generateInstructionTypes(
353 kind: 'Property',
354 objectType: value.value.identifier.type,
355 objectName: getName(names, value.value.identifier.id),
355 - propertyName: property.key.name,
356 + propertyName: makePropertyLiteral(property.key.name),
357 });
358 }
359 }
@@ -453,10 +454,12 @@ class Unifier {
454 return;
455 }
456 const objectType = this.get(tB.objectType);
456 - const propertyType = this.env.getPropertyType(
457 - objectType,
458 - tB.propertyName,
459 - );
457 + let propertyType;
458 + if (typeof tB.propertyName === 'number') {
459 + propertyType = null;
460 + } else {
461 + propertyType = this.env.getPropertyType(objectType, tB.propertyName);
462 + }
463 if (propertyType !== null) {
464 this.unify(tA, propertyType);
465 }
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts
+3 -1
@@ -257,7 +257,9 @@ export function validateHooksUsage(fn: HIRFunction): void {
257 }
258 case 'PropertyLoad': {
259 const objectKind = getKindForPlace(instr.value.object);
260 - const isHookProperty = isHookName(instr.value.property);
260 + const isHookProperty =
261 + typeof instr.value.property === 'string' &&
262 + isHookName(instr.value.property);
263 let kind: Kind;
264 switch (objectKind) {
265 case Kind.Error: {
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts
+4 -1
@@ -61,7 +61,10 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
61 }
62 case 'PropertyLoad': {
63 // Start conservative and disallow all capitalized method calls
64 - if (/^[A-Z]/.test(value.property)) {
64 + if (
65 + typeof value.property === 'string' &&
66 + /^[A-Z]/.test(value.property)
67 + ) {
68 capitalizedProperties.set(lvalue.identifier.id, value.property);
69 }
70 break;
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts
+1 -1
@@ -285,7 +285,7 @@ function validateNoRefAccessInRenderImpl(
285 }
286 case 'ComputedLoad':
287 case 'PropertyLoad': {
288 - if (typeof instr.value.property !== 'string') {
288 + if (instr.value.kind === 'ComputedLoad') {
289 validateNoDirectRefValueAccess(errors, instr.value.property, env);
290 }
291 const objType = env.get(instr.value.object.identifier.id);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mixedreadonly-mutating-map.expect.md
+9 -10
@@ -125,22 +125,21 @@ function Component(t0) {
125 } else {
126 t1 = $[2];
127 }
128 - const t2 = jsx[0];
129 - let t3;
130 - if ($[3] !== t1 || $[4] !== t2) {
131 - t3 = (
128 + let t2;
129 + if ($[3] !== jsx[0] || $[4] !== t1) {
130 + t2 = (
131 <>
132 {t1}
134 - {t2}
133 + {jsx[0]}
134 </>
135 );
137 - $[3] = t1;
138 - $[4] = t2;
139 - $[5] = t3;
136 + $[3] = jsx[0];
137 + $[4] = t1;
138 + $[5] = t2;
139 } else {
141 - t3 = $[5];
140 + t2 = $[5];
141 }
143 - return t3;
142 + return t2;
143 }
144
145 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-dep-not-recognized.expect.md deleted
-40
@@ -1,40 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validatePreserveExistingMemoizationGuarantees
6 -
7 -import {useMemo} from 'react';
8 -import {makeArray} from 'shared-runtime';
9 -
10 -// We currently only recognize "hoistable" values (e.g. variable reads
11 -// and property loads from named variables) in the source depslist.
12 -// This makes validation logic simpler and follows the same constraints
13 -// from the eslint react-hooks-deps plugin.
14 -function Foo(props) {
15 - const x = makeArray(props);
16 - // react-hooks-deps lint would already fail here
17 - return useMemo(() => [x[0]], [x[0]]);
18 -}
19 -
20 -export const FIXTURE_ENTRYPOINT = {
21 - fn: Foo,
22 - params: [{val: 1}],
23 -};
24 -
25 -```
26 -
27 -
28 -## Error
29 -
30 -```
31 - 11 | const x = makeArray(props);
32 - 12 | // react-hooks-deps lint would already fail here
33 -> 13 | return useMemo(() => [x[0]], [x[0]]);
34 - | ^^^^ InvalidReact: Expected the dependency list to be an array of simple expressions (e.g. `x`, `x.y.z`, `x?.y?.z`) (13:13)
35 - 14 | }
36 - 15 |
37 - 16 | export const FIXTURE_ENTRYPOINT = {
38 -```
39 -
40 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-dep-array-literal-access.expect.md new
+71
@@ -0,0 +1,71 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +
7 +import {useMemo} from 'react';
8 +import {makeArray} from 'shared-runtime';
9 +
10 +// We currently only recognize "hoistable" values (e.g. variable reads
11 +// and property loads from named variables) in the source depslist.
12 +// This makes validation logic simpler and follows the same constraints
13 +// from the eslint react-hooks-deps plugin.
14 +function Foo(props) {
15 + const x = makeArray(props);
16 + // react-hooks-deps lint would already fail here
17 + return useMemo(() => [x[0]], [x[0]]);
18 +}
19 +
20 +export const FIXTURE_ENTRYPOINT = {
21 + fn: Foo,
22 + params: [{val: 1}],
23 +};
24 +
25 +```
26 +
27 +## Code
28 +
29 +```javascript
30 +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
31 +
32 +import { useMemo } from "react";
33 +import { makeArray } from "shared-runtime";
34 +
35 +// We currently only recognize "hoistable" values (e.g. variable reads
36 +// and property loads from named variables) in the source depslist.
37 +// This makes validation logic simpler and follows the same constraints
38 +// from the eslint react-hooks-deps plugin.
39 +function Foo(props) {
40 + const $ = _c(4);
41 + let t0;
42 + if ($[0] !== props) {
43 + t0 = makeArray(props);
44 + $[0] = props;
45 + $[1] = t0;
46 + } else {
47 + t0 = $[1];
48 + }
49 + const x = t0;
50 + let t1;
51 + let t2;
52 + if ($[2] !== x[0]) {
53 + t2 = [x[0]];
54 + $[2] = x[0];
55 + $[3] = t2;
56 + } else {
57 + t2 = $[3];
58 + }
59 + t1 = t2;
60 + return t1;
61 +}
62 +
63 +export const FIXTURE_ENTRYPOINT = {
64 + fn: Foo,
65 + params: [{ val: 1 }],
66 +};
67 +
68 +```
69 +
70 +### Eval output
71 +(kind: ok) [{"val":1}]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-dep-array-literal-access.ts renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-via-aliased-mutation-array.expect.md
+8 -16
@@ -32,28 +32,20 @@ export const FIXTURE_ENTRYPOINT = {
32 ```javascript
33 import { c as _c } from "react/compiler-runtime";
34 function Component(props) {
35 - const $ = _c(4);
36 - let x;
35 + const $ = _c(2);
36 + let t0;
37 if ($[0] !== props.input) {
38 - x = [];
38 + const x = [];
39 const y = x;
40 y.push(props.input);
41 - $[0] = props.input;
42 - $[1] = x;
43 - } else {
44 - x = $[1];
45 - }
41
47 - const t0 = x[0];
48 - let t1;
49 - if ($[2] !== t0) {
50 - t1 = [t0];
51 - $[2] = t0;
52 - $[3] = t1;
42 + t0 = [x[0]];
43 + $[0] = props.input;
44 + $[1] = t0;
45 } else {
54 - t1 = $[3];
46 + t0 = $[1];
47 }
56 - return t1;
48 + return t0;
49 }
50
51 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-via-aliased-mutation-lambda.expect.md
+8 -16
@@ -35,32 +35,24 @@ export const FIXTURE_ENTRYPOINT = {
35 ```javascript
36 import { c as _c } from "react/compiler-runtime";
37 function Component(props) {
38 - const $ = _c(4);
39 - let x;
38 + const $ = _c(2);
39 + let t0;
40 if ($[0] !== props.input) {
41 - x = [];
41 + const x = [];
42 const f = (arg) => {
43 const y = x;
44 y.push(arg);
45 };
46
47 f(props.input);
48 - $[0] = props.input;
49 - $[1] = x;
50 - } else {
51 - x = $[1];
52 - }
48
54 - const t0 = x[0];
55 - let t1;
56 - if ($[2] !== t0) {
57 - t1 = [t0];
58 - $[2] = t0;
59 - $[3] = t1;
49 + t0 = [x[0]];
50 + $[0] = props.input;
51 + $[1] = t0;
52 } else {
61 - t1 = $[3];
53 + t0 = $[1];
54 }
63 - return t1;
55 + return t0;
56 }
57
58 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md
+38 -40
@@ -94,69 +94,67 @@ export function Component(t0) {
94 } else {
95 t6 = $[8];
96 }
97 - const t7 = items_0[0];
98 - let t8;
99 - if ($[9] !== t6 || $[10] !== t7) {
100 - t8 = <SharedRuntime.ValidateMemoization inputs={t6} output={t7} />;
101 - $[9] = t6;
102 - $[10] = t7;
103 - $[11] = t8;
97 + let t7;
98 + if ($[9] !== items_0[0] || $[10] !== t6) {
99 + t7 = <SharedRuntime.ValidateMemoization inputs={t6} output={items_0[0]} />;
100 + $[9] = items_0[0];
101 + $[10] = t6;
102 + $[11] = t7;
103 } else {
105 - t8 = $[11];
104 + t7 = $[11];
105 }
107 - let t9;
106 + let t8;
107 if ($[12] !== b) {
109 - t9 = [b];
108 + t8 = [b];
109 $[12] = b;
111 - $[13] = t9;
110 + $[13] = t8;
111 } else {
113 - t9 = $[13];
112 + t8 = $[13];
113 }
115 - const t10 = items_0[1];
116 - let t11;
117 - if ($[14] !== t10 || $[15] !== t9) {
118 - t11 = <SharedRuntime.ValidateMemoization inputs={t9} output={t10} />;
119 - $[14] = t10;
120 - $[15] = t9;
121 - $[16] = t11;
114 + let t9;
115 + if ($[14] !== items_0[1] || $[15] !== t8) {
116 + t9 = <SharedRuntime.ValidateMemoization inputs={t8} output={items_0[1]} />;
117 + $[14] = items_0[1];
118 + $[15] = t8;
119 + $[16] = t9;
120 } else {
123 - t11 = $[16];
121 + t9 = $[16];
122 }
125 - let t12;
123 + let t10;
124 if ($[17] !== a || $[18] !== b) {
127 - t12 = [a, b];
125 + t10 = [a, b];
126 $[17] = a;
127 $[18] = b;
130 - $[19] = t12;
128 + $[19] = t10;
129 } else {
132 - t12 = $[19];
130 + t10 = $[19];
131 }
134 - let t13;
135 - if ($[20] !== items_0 || $[21] !== t12) {
136 - t13 = <SharedRuntime.ValidateMemoization inputs={t12} output={items_0} />;
132 + let t11;
133 + if ($[20] !== items_0 || $[21] !== t10) {
134 + t11 = <SharedRuntime.ValidateMemoization inputs={t10} output={items_0} />;
135 $[20] = items_0;
138 - $[21] = t12;
139 - $[22] = t13;
136 + $[21] = t10;
137 + $[22] = t11;
138 } else {
141 - t13 = $[22];
139 + t11 = $[22];
140 }
143 - let t14;
144 - if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) {
145 - t14 = (
141 + let t12;
142 + if ($[23] !== t11 || $[24] !== t7 || $[25] !== t9) {
143 + t12 = (
144 <>
147 - {t8}
145 + {t7}
146 + {t9}
147 {t11}
149 - {t13}
148 </>
149 );
150 $[23] = t11;
153 - $[24] = t13;
154 - $[25] = t8;
155 - $[26] = t14;
151 + $[24] = t7;
152 + $[25] = t9;
153 + $[26] = t12;
154 } else {
157 - t14 = $[26];
155 + t12 = $[26];
156 }
159 - return t14;
157 + return t12;
158 }
159
160 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md
+38 -40
@@ -94,69 +94,67 @@ export function Component(t0) {
94 } else {
95 t6 = $[8];
96 }
97 - const t7 = items_0[0];
98 - let t8;
99 - if ($[9] !== t6 || $[10] !== t7) {
100 - t8 = <ValidateMemoization inputs={t6} output={t7} />;
101 - $[9] = t6;
102 - $[10] = t7;
103 - $[11] = t8;
97 + let t7;
98 + if ($[9] !== items_0[0] || $[10] !== t6) {
99 + t7 = <ValidateMemoization inputs={t6} output={items_0[0]} />;
100 + $[9] = items_0[0];
101 + $[10] = t6;
102 + $[11] = t7;
103 } else {
105 - t8 = $[11];
104 + t7 = $[11];
105 }
107 - let t9;
106 + let t8;
107 if ($[12] !== b) {
109 - t9 = [b];
108 + t8 = [b];
109 $[12] = b;
111 - $[13] = t9;
110 + $[13] = t8;
111 } else {
113 - t9 = $[13];
112 + t8 = $[13];
113 }
115 - const t10 = items_0[1];
116 - let t11;
117 - if ($[14] !== t10 || $[15] !== t9) {
118 - t11 = <ValidateMemoization inputs={t9} output={t10} />;
119 - $[14] = t10;
120 - $[15] = t9;
121 - $[16] = t11;
114 + let t9;
115 + if ($[14] !== items_0[1] || $[15] !== t8) {
116 + t9 = <ValidateMemoization inputs={t8} output={items_0[1]} />;
117 + $[14] = items_0[1];
118 + $[15] = t8;
119 + $[16] = t9;
120 } else {
123 - t11 = $[16];
121 + t9 = $[16];
122 }
125 - let t12;
123 + let t10;
124 if ($[17] !== a || $[18] !== b) {
127 - t12 = [a, b];
125 + t10 = [a, b];
126 $[17] = a;
127 $[18] = b;
130 - $[19] = t12;
128 + $[19] = t10;
129 } else {
132 - t12 = $[19];
130 + t10 = $[19];
131 }
134 - let t13;
135 - if ($[20] !== items_0 || $[21] !== t12) {
136 - t13 = <ValidateMemoization inputs={t12} output={items_0} />;
132 + let t11;
133 + if ($[20] !== items_0 || $[21] !== t10) {
134 + t11 = <ValidateMemoization inputs={t10} output={items_0} />;
135 $[20] = items_0;
138 - $[21] = t12;
139 - $[22] = t13;
136 + $[21] = t10;
137 + $[22] = t11;
138 } else {
141 - t13 = $[22];
139 + t11 = $[22];
140 }
143 - let t14;
144 - if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) {
145 - t14 = (
141 + let t12;
142 + if ($[23] !== t11 || $[24] !== t7 || $[25] !== t9) {
143 + t12 = (
144 <>
147 - {t8}
145 + {t7}
146 + {t9}
147 {t11}
149 - {t13}
148 </>
149 );
150 $[23] = t11;
153 - $[24] = t13;
154 - $[25] = t8;
155 - $[26] = t14;
151 + $[24] = t7;
152 + $[25] = t9;
153 + $[26] = t12;
154 } else {
157 - t14 = $[26];
155 + t12 = $[26];
156 }
159 - return t14;
157 + return t12;
158 }
159
160 export const FIXTURE_ENTRYPOINT = {