@samitouri / QOS-React / commits / a8e503dce0

[compiler][optim] Add map and set constructors (#32697)

* Adds `isConstructor: boolean` to `FunctionType`. With this PR, each typed function can either be a constructor (currently only known globals) or non constructor. Alternatively, we prefer to encode polymorphic types / effects (and match the closest subtype) * Add Map and Set globals + built-ins --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32697). * #32698 * __->__ #32697

mofeiZ committed Mar 23, 2025 at 23:19 UTC a8e503dce0ec386eef752a1219dd6ef861c48ced
18 files changed +1017 -51
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
+34
@@ -10,8 +10,10 @@ import {
10 BUILTIN_SHAPES,
11 BuiltInArrayId,
12 BuiltInFireId,
13 + BuiltInMapId,
14 BuiltInMixedReadonlyId,
15 BuiltInObjectId,
16 + BuiltInSetId,
17 BuiltInUseActionStateId,
18 BuiltInUseContextHookId,
19 BuiltInUseEffectHookId,
@@ -458,6 +460,38 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
460 returnValueKind: ValueKind.Primitive,
461 }),
462 ],
463 + [
464 + 'Map',
465 + addFunction(
466 + DEFAULT_SHAPES,
467 + [],
468 + {
469 + positionalParams: [Effect.ConditionallyMutate],
470 + restParam: null,
471 + returnType: {kind: 'Object', shapeId: BuiltInMapId},
472 + calleeEffect: Effect.Read,
473 + returnValueKind: ValueKind.Mutable,
474 + },
475 + null,
476 + true,
477 + ),
478 + ],
479 + [
480 + 'Set',
481 + addFunction(
482 + DEFAULT_SHAPES,
483 + [],
484 + {
485 + positionalParams: [Effect.ConditionallyMutate],
486 + restParam: null,
487 + returnType: {kind: 'Object', shapeId: BuiltInSetId},
488 + calleeEffect: Effect.Read,
489 + returnValueKind: ValueKind.Mutable,
490 + },
491 + null,
492 + true,
493 + ),
494 + ],
495 // TODO: rest of Global objects
496 ];
497
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+17 -7
@@ -10,7 +10,7 @@ import * as t from '@babel/types';
10 import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
11 import {assertExhaustive} from '../Utils/utils';
12 import {Environment, ReactFunctionType} from './Environment';
13 -import {HookKind} from './ObjectShape';
13 +import type {HookKind} from './ObjectShape';
14 import {Type, makeType} from './Types';
15 import {z} from 'zod';
16
@@ -829,6 +829,13 @@ export type CallExpression = {
829 typeArguments?: Array<t.FlowType>;
830 };
831
832 +export type NewExpression = {
833 + kind: 'NewExpression';
834 + callee: Place;
835 + args: Array<Place | SpreadPattern>;
836 + loc: SourceLocation;
837 +};
838 +
839 export type LoadLocal = {
840 kind: 'LoadLocal';
841 place: Place;
@@ -894,12 +901,7 @@ export type InstructionValue =
901 right: Place;
902 loc: SourceLocation;
903 }
897 - | {
898 - kind: 'NewExpression';
899 - callee: Place;
900 - args: Array<Place | SpreadPattern>;
901 - loc: SourceLocation;
902 - }
904 + | NewExpression
905 | CallExpression
906 | MethodCall
907 | {
@@ -1649,6 +1651,14 @@ export function isArrayType(id: Identifier): boolean {
1651 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
1652 }
1653
1654 +export function isMapType(id: Identifier): boolean {
1655 + return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInMap';
1656 +}
1657 +
1658 +export function isSetType(id: Identifier): boolean {
1659 + return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInSet';
1660 +}
1661 +
1662 export function isPropsType(id: Identifier): boolean {
1663 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
1664 }
compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts
+312
@@ -44,6 +44,7 @@ export function addFunction(
44 properties: Iterable<[string, BuiltInType | PolyType]>,
45 fn: Omit<FunctionSignature, 'hookKind'>,
46 id: string | null = null,
47 + isConstructor: boolean = false,
48 ): FunctionType {
49 const shapeId = id ?? createAnonId();
50 addShape(registry, shapeId, properties, {
@@ -54,6 +55,7 @@ export function addFunction(
55 kind: 'Function',
56 return: fn.returnType,
57 shapeId,
58 + isConstructor,
59 };
60 }
61
@@ -73,6 +75,7 @@ export function addHook(
75 kind: 'Function',
76 return: fn.returnType,
77 shapeId,
78 + isConstructor: false,
79 };
80 }
81
@@ -198,6 +201,8 @@ export type ObjectShape = {
201 export type ShapeRegistry = Map<string, ObjectShape>;
202 export const BuiltInPropsId = 'BuiltInProps';
203 export const BuiltInArrayId = 'BuiltInArray';
204 +export const BuiltInSetId = 'BuiltInSet';
205 +export const BuiltInMapId = 'BuiltInMap';
206 export const BuiltInFunctionId = 'BuiltInFunction';
207 export const BuiltInJsxId = 'BuiltInJsx';
208 export const BuiltInObjectId = 'BuiltInObject';
@@ -451,6 +456,313 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
456 */
457 ]);
458
459 +/* Built-in Set shape */
460 +addObject(BUILTIN_SHAPES, BuiltInSetId, [
461 + [
462 + /**
463 + * add(value)
464 + * Parameters
465 + * value: the value of the element to add to the Set object.
466 + * Returns the Set object with added value.
467 + */
468 + 'add',
469 + addFunction(BUILTIN_SHAPES, [], {
470 + positionalParams: [Effect.Capture],
471 + restParam: null,
472 + returnType: {kind: 'Object', shapeId: BuiltInSetId},
473 + calleeEffect: Effect.Store,
474 + // returnValueKind is technically dependent on the ValueKind of the set itself
475 + returnValueKind: ValueKind.Mutable,
476 + }),
477 + ],
478 + [
479 + /**
480 + * clear()
481 + * Parameters none
482 + * Returns undefined
483 + */
484 + 'clear',
485 + addFunction(BUILTIN_SHAPES, [], {
486 + positionalParams: [],
487 + restParam: null,
488 + returnType: PRIMITIVE_TYPE,
489 + calleeEffect: Effect.Store,
490 + returnValueKind: ValueKind.Primitive,
491 + }),
492 + ],
493 + [
494 + /**
495 + * setInstance.delete(value)
496 + * Returns true if value was already in Set; otherwise false.
497 + */
498 + 'delete',
499 + addFunction(BUILTIN_SHAPES, [], {
500 + positionalParams: [Effect.Read],
501 + restParam: null,
502 + returnType: PRIMITIVE_TYPE,
503 + calleeEffect: Effect.Store,
504 + returnValueKind: ValueKind.Primitive,
505 + }),
506 + ],
507 + [
508 + 'has',
509 + addFunction(BUILTIN_SHAPES, [], {
510 + positionalParams: [Effect.Read],
511 + restParam: null,
512 + returnType: PRIMITIVE_TYPE,
513 + calleeEffect: Effect.Read,
514 + returnValueKind: ValueKind.Primitive,
515 + }),
516 + ],
517 + ['size', PRIMITIVE_TYPE],
518 + [
519 + /**
520 + * difference(other)
521 + * Parameters
522 + * other: A Set object, or set-like object.
523 + * Returns a new Set object containing elements in this set but not in the other set.
524 + */
525 + 'difference',
526 + addFunction(BUILTIN_SHAPES, [], {
527 + positionalParams: [Effect.Capture],
528 + restParam: null,
529 + returnType: {kind: 'Object', shapeId: BuiltInSetId},
530 + calleeEffect: Effect.Capture,
531 + returnValueKind: ValueKind.Mutable,
532 + }),
533 + ],
534 + [
535 + /**
536 + * union(other)
537 + * Parameters
538 + * other: A Set object, or set-like object.
539 + * Returns a new Set object containing elements in either this set or the other set.
540 + */
541 + 'union',
542 + addFunction(BUILTIN_SHAPES, [], {
543 + positionalParams: [Effect.Capture],
544 + restParam: null,
545 + returnType: {kind: 'Object', shapeId: BuiltInSetId},
546 + calleeEffect: Effect.Capture,
547 + returnValueKind: ValueKind.Mutable,
548 + }),
549 + ],
550 + [
551 + /**
552 + * symmetricalDifference(other)
553 + * Parameters
554 + * other: A Set object, or set-like object.
555 + * A new Set object containing elements which are in either this set or the other set, but not in both.
556 + */
557 + 'symmetricalDifference',
558 + addFunction(BUILTIN_SHAPES, [], {
559 + positionalParams: [Effect.Capture],
560 + restParam: null,
561 + returnType: {kind: 'Object', shapeId: BuiltInSetId},
562 + calleeEffect: Effect.Capture,
563 + returnValueKind: ValueKind.Mutable,
564 + }),
565 + ],
566 + [
567 + /**
568 + * isSubsetOf(other)
569 + * Parameters
570 + * other: A Set object, or set-like object.
571 + * Returns true if all elements in this set are also in the other set, and false otherwise.
572 + */
573 + 'isSubsetOf',
574 + addFunction(BUILTIN_SHAPES, [], {
575 + positionalParams: [Effect.Read],
576 + restParam: null,
577 + returnType: PRIMITIVE_TYPE,
578 + calleeEffect: Effect.Read,
579 + returnValueKind: ValueKind.Primitive,
580 + }),
581 + ],
582 + [
583 + /**
584 + * isSupersetOf(other)
585 + * Parameters
586 + * other: A Set object, or set-like object.
587 + * Returns true if all elements in the other set are also in this set, and false otherwise.
588 + */
589 + 'isSupersetOf',
590 + addFunction(BUILTIN_SHAPES, [], {
591 + positionalParams: [Effect.Read],
592 + restParam: null,
593 + returnType: PRIMITIVE_TYPE,
594 + calleeEffect: Effect.Read,
595 + returnValueKind: ValueKind.Primitive,
596 + }),
597 + ],
598 + [
599 + /**
600 + * forEach(callbackFn)
601 + * forEach(callbackFn, thisArg)
602 + */
603 + 'forEach',
604 + addFunction(BUILTIN_SHAPES, [], {
605 + /**
606 + * see Array.map explanation for why arguments are marked `ConditionallyMutate`
607 + */
608 + positionalParams: [],
609 + restParam: Effect.ConditionallyMutate,
610 + returnType: PRIMITIVE_TYPE,
611 + calleeEffect: Effect.ConditionallyMutate,
612 + returnValueKind: ValueKind.Primitive,
613 + noAlias: true,
614 + mutableOnlyIfOperandsAreMutable: true,
615 + }),
616 + ],
617 + /**
618 + * Iterators
619 + */
620 + [
621 + 'entries',
622 + addFunction(BUILTIN_SHAPES, [], {
623 + positionalParams: [],
624 + restParam: null,
625 + returnType: {kind: 'Poly'},
626 + calleeEffect: Effect.Capture,
627 + returnValueKind: ValueKind.Mutable,
628 + }),
629 + ],
630 + [
631 + 'keys',
632 + addFunction(BUILTIN_SHAPES, [], {
633 + positionalParams: [],
634 + restParam: null,
635 + returnType: {kind: 'Poly'},
636 + calleeEffect: Effect.Capture,
637 + returnValueKind: ValueKind.Mutable,
638 + }),
639 + ],
640 + [
641 + 'values',
642 + addFunction(BUILTIN_SHAPES, [], {
643 + positionalParams: [],
644 + restParam: null,
645 + returnType: {kind: 'Poly'},
646 + calleeEffect: Effect.Capture,
647 + returnValueKind: ValueKind.Mutable,
648 + }),
649 + ],
650 +]);
651 +addObject(BUILTIN_SHAPES, BuiltInMapId, [
652 + [
653 + /**
654 + * clear()
655 + * Parameters none
656 + * Returns undefined
657 + */
658 + 'clear',
659 + addFunction(BUILTIN_SHAPES, [], {
660 + positionalParams: [],
661 + restParam: null,
662 + returnType: PRIMITIVE_TYPE,
663 + calleeEffect: Effect.Store,
664 + returnValueKind: ValueKind.Primitive,
665 + }),
666 + ],
667 + [
668 + 'delete',
669 + addFunction(BUILTIN_SHAPES, [], {
670 + positionalParams: [Effect.Read],
671 + restParam: null,
672 + returnType: PRIMITIVE_TYPE,
673 + calleeEffect: Effect.Store,
674 + returnValueKind: ValueKind.Primitive,
675 + }),
676 + ],
677 + [
678 + 'get',
679 + addFunction(BUILTIN_SHAPES, [], {
680 + positionalParams: [Effect.Read],
681 + restParam: null,
682 + returnType: {kind: 'Poly'},
683 + calleeEffect: Effect.Capture,
684 + returnValueKind: ValueKind.Mutable,
685 + }),
686 + ],
687 + [
688 + 'has',
689 + addFunction(BUILTIN_SHAPES, [], {
690 + positionalParams: [Effect.Read],
691 + restParam: null,
692 + returnType: PRIMITIVE_TYPE,
693 + calleeEffect: Effect.Read,
694 + returnValueKind: ValueKind.Primitive,
695 + }),
696 + ],
697 + [
698 + /**
699 + * Params
700 + * key: the key of the element to add to the Map object. The key may be
701 + * any JavaScript type (any primitive value or any type of JavaScript
702 + * object).
703 + * value: the value of the element to add to the Map object.
704 + * Returns the Map object.
705 + */
706 + 'set',
707 + addFunction(BUILTIN_SHAPES, [], {
708 + positionalParams: [Effect.Capture, Effect.Capture],
709 + restParam: null,
710 + returnType: {kind: 'Object', shapeId: BuiltInMapId},
711 + calleeEffect: Effect.Store,
712 + returnValueKind: ValueKind.Mutable,
713 + }),
714 + ],
715 + ['size', PRIMITIVE_TYPE],
716 + [
717 + 'forEach',
718 + addFunction(BUILTIN_SHAPES, [], {
719 + /**
720 + * see Array.map explanation for why arguments are marked `ConditionallyMutate`
721 + */
722 + positionalParams: [],
723 + restParam: Effect.ConditionallyMutate,
724 + returnType: PRIMITIVE_TYPE,
725 + calleeEffect: Effect.ConditionallyMutate,
726 + returnValueKind: ValueKind.Primitive,
727 + noAlias: true,
728 + mutableOnlyIfOperandsAreMutable: true,
729 + }),
730 + ],
731 + /**
732 + * Iterators
733 + */
734 + [
735 + 'entries',
736 + addFunction(BUILTIN_SHAPES, [], {
737 + positionalParams: [],
738 + restParam: null,
739 + returnType: {kind: 'Poly'},
740 + calleeEffect: Effect.Capture,
741 + returnValueKind: ValueKind.Mutable,
742 + }),
743 + ],
744 + [
745 + 'keys',
746 + addFunction(BUILTIN_SHAPES, [], {
747 + positionalParams: [],
748 + restParam: null,
749 + returnType: {kind: 'Poly'},
750 + calleeEffect: Effect.Capture,
751 + returnValueKind: ValueKind.Mutable,
752 + }),
753 + ],
754 + [
755 + 'values',
756 + addFunction(BUILTIN_SHAPES, [], {
757 + positionalParams: [],
758 + restParam: null,
759 + returnType: {kind: 'Poly'},
760 + calleeEffect: Effect.Capture,
761 + returnValueKind: ValueKind.Mutable,
762 + }),
763 + ],
764 +]);
765 +
766 addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
767 ['0', {kind: 'Poly'}],
768 [
compiler/packages/babel-plugin-react-compiler/src/HIR/Types.ts
+2
@@ -38,6 +38,7 @@ export type FunctionType = {
38 kind: 'Function';
39 shapeId: string | null;
40 return: Type;
41 + isConstructor: boolean;
42 };
43
44 export type ObjectType = {
@@ -111,6 +112,7 @@ export function duplicateType(type: Type): Type {
112 kind: 'Function',
113 return: duplicateType(type.return),
114 shapeId: type.shapeId,
115 + isConstructor: type.isConstructor,
116 };
117 }
118 case 'Object': {
compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts
+28 -42
@@ -12,6 +12,7 @@ import {
12 BasicBlock,
13 BlockId,
14 CallExpression,
15 + NewExpression,
16 Effect,
17 FunctionEffect,
18 GeneratedSource,
@@ -39,7 +40,6 @@ import {
40 printSourceLocation,
41 } from '../HIR/PrintHIR';
42 import {
42 - eachCallArgument,
43 eachInstructionOperand,
44 eachInstructionValueOperand,
45 eachPatternOperand,
@@ -905,43 +905,12 @@ function inferBlock(
905 break;
906 }
907 case 'NewExpression': {
908 - /**
909 - * For new expressions, we infer a `read` effect on the Class / Function type
910 - * to avoid extending mutable ranges of locally created classes, e.g.
911 - * ```js
912 - * const MyClass = getClass();
913 - * const value = new MyClass(val1, val2)
914 - * ^ (read) ^ (conditionally mutate)
915 - * ```
916 - *
917 - * Risks:
918 - * Classes / functions created during render could technically capture and
919 - * mutate their enclosing scope, which we currently do not detect.
920 - */
921 - const valueKind: AbstractValue = {
922 - kind: ValueKind.Mutable,
923 - reason: new Set([ValueReason.Other]),
924 - context: new Set(),
925 - };
926 - state.referenceAndRecordEffects(
908 + inferCallEffects(
909 + state,
910 + instr as TInstruction<NewExpression>,
911 freezeActions,
928 - instrValue.callee,
929 - Effect.Read,
930 - ValueReason.Other,
912 + getFunctionCallSignature(env, instrValue.callee.identifier.type),
913 );
932 -
933 - for (const operand of eachCallArgument(instrValue.args)) {
934 - state.referenceAndRecordEffects(
935 - freezeActions,
936 - operand,
937 - Effect.ConditionallyMutate,
938 - ValueReason.Other,
939 - );
940 - }
941 -
942 - state.initialize(instrValue, valueKind);
943 - state.define(instr.lvalue, instrValue);
944 - instr.lvalue.effect = Effect.ConditionallyMutate;
914 continuation = {kind: 'funeffects'};
915 break;
916 }
@@ -1844,7 +1813,7 @@ export function getFunctionCallSignature(
1813 * @returns Inferred effects of function arguments, or null if inference fails.
1814 */
1815 export function getFunctionEffects(
1847 - fn: MethodCall | CallExpression,
1816 + fn: MethodCall | CallExpression | NewExpression,
1817 sig: FunctionSignature,
1818 ): Array<Effect> | null {
1819 const results = [];
@@ -1989,7 +1958,10 @@ function getArgumentEffect(
1958
1959 function inferCallEffects(
1960 state: InferenceState,
1992 - instr: TInstruction<CallExpression> | TInstruction<MethodCall>,
1961 + instr:
1962 + | TInstruction<CallExpression>
1963 + | TInstruction<MethodCall>
1964 + | TInstruction<NewExpression>,
1965 freezeActions: Array<FreezeAction>,
1966 signature: FunctionSignature | null,
1967 ): void {
@@ -2062,9 +2034,7 @@ function inferCallEffects(
2034 hasCaptureArgument ||= place.effect === Effect.Capture;
2035 }
2036 const callee =
2065 - instrValue.kind === 'CallExpression'
2066 - ? instrValue.callee
2067 - : instrValue.receiver;
2037 + instrValue.kind === 'MethodCall' ? instrValue.receiver : instrValue.callee;
2038 if (signature !== null) {
2039 state.referenceAndRecordEffects(
2040 freezeActions,
@@ -2073,10 +2043,26 @@ function inferCallEffects(
2043 ValueReason.Other,
2044 );
2045 } else {
2046 + /**
2047 + * For new expressions, we infer a `read` effect on the Class / Function type
2048 + * to avoid extending mutable ranges of locally created classes, e.g.
2049 + * ```js
2050 + * const MyClass = getClass();
2051 + * const value = new MyClass(val1, val2)
2052 + * ^ (read) ^ (conditionally mutate)
2053 + * ```
2054 + *
2055 + * Risks:
2056 + * Classes / functions created during render could technically capture and
2057 + * mutate their enclosing scope, which we currently do not detect.
2058 + */
2059 +
2060 state.referenceAndRecordEffects(
2061 freezeActions,
2062 callee,
2079 - Effect.ConditionallyMutate,
2063 + instrValue.kind === 'NewExpression'
2064 + ? Effect.Read
2065 + : Effect.ConditionallyMutate,
2066 ValueReason.Other,
2067 );
2068 }
compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts
+22 -2
@@ -261,6 +261,7 @@ function* generateInstructionTypes(
261 kind: 'Function',
262 shapeId: null,
263 return: returnType,
264 + isConstructor: false,
265 });
266 yield equation(left, returnType);
267 break;
@@ -277,6 +278,7 @@ function* generateInstructionTypes(
278 kind: 'Function',
279 shapeId: null,
280 return: returnType,
281 + isConstructor: false,
282 });
283 yield equation(left, returnType);
284 break;
@@ -333,6 +335,7 @@ function* generateInstructionTypes(
335 kind: 'Function',
336 return: returnType,
337 shapeId: null,
338 + isConstructor: false,
339 });
340
341 yield equation(left, returnType);
@@ -405,6 +408,7 @@ function* generateInstructionTypes(
408 kind: 'Function',
409 shapeId: BuiltInFunctionId,
410 return: value.loweredFunc.func.returnType,
411 + isConstructor: false,
412 });
413 break;
414 }
@@ -425,9 +429,20 @@ function* generateInstructionTypes(
429 yield equation(left, {kind: 'Object', shapeId: BuiltInJsxId});
430 break;
431 }
432 + case 'NewExpression': {
433 + const returnType = makeType();
434 + yield equation(value.callee.identifier.type, {
435 + kind: 'Function',
436 + return: returnType,
437 + shapeId: null,
438 + isConstructor: true,
439 + });
440 +
441 + yield equation(left, returnType);
442 + break;
443 + }
444 case 'PropertyStore':
445 case 'DeclareLocal':
430 - case 'NewExpression':
446 case 'RegExpLiteral':
447 case 'MetaProperty':
448 case 'ComputedStore':
@@ -505,7 +520,11 @@ class Unifier {
520 return;
521 }
522
508 - if (tB.kind === 'Function' && tA.kind === 'Function') {
523 + if (
524 + tB.kind === 'Function' &&
525 + tA.kind === 'Function' &&
526 + tA.isConstructor === tB.isConstructor
527 + ) {
528 this.unify(tA.return, tB.return);
529 return;
530 }
@@ -648,6 +667,7 @@ class Unifier {
667 kind: 'Function',
668 return: returnType,
669 shapeId: type.shapeId,
670 + isConstructor: type.isConstructor,
671 };
672 }
673 case 'ObjectMethod':
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/map-constructor.expect.md new
+77
@@ -0,0 +1,77 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {makeArray} from 'shared-runtime';
6 +
7 +function useHook({el1, el2}) {
8 + const s = new Map();
9 + s.set(el1, makeArray(el1));
10 + s.set(el2, makeArray(el2));
11 + return s.size;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: useHook,
16 + params: [{el1: 1, el2: 'foo'}],
17 + sequentialRenders: [
18 + {el1: 1, el2: 'foo'},
19 + {el1: 2, el2: 'foo'},
20 + ],
21 +};
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +import { c as _c } from "react/compiler-runtime";
29 +import { makeArray } from "shared-runtime";
30 +
31 +function useHook(t0) {
32 + const $ = _c(7);
33 + const { el1, el2 } = t0;
34 + let s;
35 + if ($[0] !== el1 || $[1] !== el2) {
36 + s = new Map();
37 + let t1;
38 + if ($[3] !== el1) {
39 + t1 = makeArray(el1);
40 + $[3] = el1;
41 + $[4] = t1;
42 + } else {
43 + t1 = $[4];
44 + }
45 + s.set(el1, t1);
46 + let t2;
47 + if ($[5] !== el2) {
48 + t2 = makeArray(el2);
49 + $[5] = el2;
50 + $[6] = t2;
51 + } else {
52 + t2 = $[6];
53 + }
54 + s.set(el2, t2);
55 + $[0] = el1;
56 + $[1] = el2;
57 + $[2] = s;
58 + } else {
59 + s = $[2];
60 + }
61 + return s.size;
62 +}
63 +
64 +export const FIXTURE_ENTRYPOINT = {
65 + fn: useHook,
66 + params: [{ el1: 1, el2: "foo" }],
67 + sequentialRenders: [
68 + { el1: 1, el2: "foo" },
69 + { el1: 2, el2: "foo" },
70 + ],
71 +};
72 +
73 +```
74 +
75 +### Eval output
76 +(kind: ok) 2
77 +2
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/map-constructor.ts new
+17
@@ -0,0 +1,17 @@
1 +import {makeArray} from 'shared-runtime';
2 +
3 +function useHook({el1, el2}) {
4 + const s = new Map();
5 + s.set(el1, makeArray(el1));
6 + s.set(el2, makeArray(el2));
7 + return s.size;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: useHook,
12 + params: [{el1: 1, el2: 'foo'}],
13 + sequentialRenders: [
14 + {el1: 1, el2: 'foo'},
15 + {el1: 2, el2: 'foo'},
16 + ],
17 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-add-mutate.expect.md new
+76
@@ -0,0 +1,76 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {makeArray} from 'shared-runtime';
6 +
7 +function useHook({el1, el2}) {
8 + const s = new Set();
9 + const arr = makeArray(el1);
10 + s.add(arr);
11 + // Mutate after store
12 + arr.push(el2);
13 +
14 + s.add(makeArray(el2));
15 + return s.size;
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: useHook,
20 + params: [{el1: 1, el2: 'foo'}],
21 + sequentialRenders: [
22 + {el1: 1, el2: 'foo'},
23 + {el1: 2, el2: 'foo'},
24 + ],
25 +};
26 +
27 +```
28 +
29 +## Code
30 +
31 +```javascript
32 +import { c as _c } from "react/compiler-runtime";
33 +import { makeArray } from "shared-runtime";
34 +
35 +function useHook(t0) {
36 + const $ = _c(5);
37 + const { el1, el2 } = t0;
38 + let s;
39 + if ($[0] !== el1 || $[1] !== el2) {
40 + s = new Set();
41 + const arr = makeArray(el1);
42 + s.add(arr);
43 +
44 + arr.push(el2);
45 + let t1;
46 + if ($[3] !== el2) {
47 + t1 = makeArray(el2);
48 + $[3] = el2;
49 + $[4] = t1;
50 + } else {
51 + t1 = $[4];
52 + }
53 + s.add(t1);
54 + $[0] = el1;
55 + $[1] = el2;
56 + $[2] = s;
57 + } else {
58 + s = $[2];
59 + }
60 + return s.size;
61 +}
62 +
63 +export const FIXTURE_ENTRYPOINT = {
64 + fn: useHook,
65 + params: [{ el1: 1, el2: "foo" }],
66 + sequentialRenders: [
67 + { el1: 1, el2: "foo" },
68 + { el1: 2, el2: "foo" },
69 + ],
70 +};
71 +
72 +```
73 +
74 +### Eval output
75 +(kind: ok) 2
76 +2
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-add-mutate.ts new
+21
@@ -0,0 +1,21 @@
1 +import {makeArray} from 'shared-runtime';
2 +
3 +function useHook({el1, el2}) {
4 + const s = new Set();
5 + const arr = makeArray(el1);
6 + s.add(arr);
7 + // Mutate after store
8 + arr.push(el2);
9 +
10 + s.add(makeArray(el2));
11 + return s.size;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: useHook,
16 + params: [{el1: 1, el2: 'foo'}],
17 + sequentialRenders: [
18 + {el1: 1, el2: 'foo'},
19 + {el1: 2, el2: 'foo'},
20 + ],
21 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.expect.md new
+98
@@ -0,0 +1,98 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +const MODULE_LOCAL = new Set([4, 5, 6]);
6 +function useFoo({propArr}: {propArr: Array<number>}) {
7 + /* TODO: Array can be memoized separately of the Set */
8 + const s1 = new Set([1, 2, 3]);
9 + s1.add(propArr[0]);
10 +
11 + /* but `.values` cannot be memoized separately */
12 + const s2 = new Set(MODULE_LOCAL.values());
13 + s2.add(propArr[1]);
14 +
15 + const s3 = new Set(s2.values());
16 + s3.add(propArr[2]);
17 +
18 + /**
19 + * TODO: s3 should be memoized separately of s4
20 + */
21 + const s4 = new Set(s3);
22 + s4.add(propArr[3]);
23 + return [s1, s2, s3, s4];
24 +}
25 +
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: useFoo,
28 + params: [{propArr: [7, 8, 9]}],
29 + sequentialRenders: [{propArr: [7, 8, 9]}, {propArr: [7, 8, 10]}],
30 +};
31 +
32 +```
33 +
34 +## Code
35 +
36 +```javascript
37 +import { c as _c } from "react/compiler-runtime";
38 +const MODULE_LOCAL = new Set([4, 5, 6]);
39 +function useFoo(t0) {
40 + const $ = _c(13);
41 + const { propArr } = t0;
42 + let s1;
43 + if ($[0] !== propArr[0]) {
44 + s1 = new Set([1, 2, 3]);
45 + s1.add(propArr[0]);
46 + $[0] = propArr[0];
47 + $[1] = s1;
48 + } else {
49 + s1 = $[1];
50 + }
51 + let s2;
52 + let s3;
53 + let s4;
54 + if ($[2] !== propArr[1] || $[3] !== propArr[2] || $[4] !== propArr[3]) {
55 + s2 = new Set(MODULE_LOCAL.values());
56 + s2.add(propArr[1]);
57 +
58 + s3 = new Set(s2.values());
59 + s3.add(propArr[2]);
60 +
61 + s4 = new Set(s3);
62 + s4.add(propArr[3]);
63 + $[2] = propArr[1];
64 + $[3] = propArr[2];
65 + $[4] = propArr[3];
66 + $[5] = s2;
67 + $[6] = s3;
68 + $[7] = s4;
69 + } else {
70 + s2 = $[5];
71 + s3 = $[6];
72 + s4 = $[7];
73 + }
74 + let t1;
75 + if ($[8] !== s1 || $[9] !== s2 || $[10] !== s3 || $[11] !== s4) {
76 + t1 = [s1, s2, s3, s4];
77 + $[8] = s1;
78 + $[9] = s2;
79 + $[10] = s3;
80 + $[11] = s4;
81 + $[12] = t1;
82 + } else {
83 + t1 = $[12];
84 + }
85 + return t1;
86 +}
87 +
88 +export const FIXTURE_ENTRYPOINT = {
89 + fn: useFoo,
90 + params: [{ propArr: [7, 8, 9] }],
91 + sequentialRenders: [{ propArr: [7, 8, 9] }, { propArr: [7, 8, 10] }],
92 +};
93 +
94 +```
95 +
96 +### Eval output
97 +(kind: ok) [{"kind":"Set","value":[1,2,3,7]},{"kind":"Set","value":[4,5,6,8]},{"kind":"Set","value":[4,5,6,8,9]},{"kind":"Set","value":[4,5,6,8,9,null]}]
98 +[{"kind":"Set","value":[1,2,3,7]},{"kind":"Set","value":[4,5,6,8]},{"kind":"Set","value":[4,5,6,8,10]},{"kind":"Set","value":[4,5,6,8,10,null]}]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.ts new
+26
@@ -0,0 +1,26 @@
1 +const MODULE_LOCAL = new Set([4, 5, 6]);
2 +function useFoo({propArr}: {propArr: Array<number>}) {
3 + /* TODO: Array can be memoized separately of the Set */
4 + const s1 = new Set([1, 2, 3]);
5 + s1.add(propArr[0]);
6 +
7 + /* but `.values` cannot be memoized separately */
8 + const s2 = new Set(MODULE_LOCAL.values());
9 + s2.add(propArr[1]);
10 +
11 + const s3 = new Set(s2.values());
12 + s3.add(propArr[2]);
13 +
14 + /**
15 + * TODO: s3 should be memoized separately of s4
16 + */
17 + const s4 = new Set(s3);
18 + s4.add(propArr[3]);
19 + return [s1, s2, s3, s4];
20 +}
21 +
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: useFoo,
24 + params: [{propArr: [7, 8, 9]}],
25 + sequentialRenders: [{propArr: [7, 8, 9]}, {propArr: [7, 8, 10]}],
26 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor.expect.md new
+77
@@ -0,0 +1,77 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {makeArray} from 'shared-runtime';
6 +
7 +function useHook({el1, el2}) {
8 + const s = new Set();
9 + s.add(makeArray(el1));
10 + s.add(makeArray(el2));
11 + return s.size;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: useHook,
16 + params: [{el1: 1, el2: 'foo'}],
17 + sequentialRenders: [
18 + {el1: 1, el2: 'foo'},
19 + {el1: 2, el2: 'foo'},
20 + ],
21 +};
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +import { c as _c } from "react/compiler-runtime";
29 +import { makeArray } from "shared-runtime";
30 +
31 +function useHook(t0) {
32 + const $ = _c(7);
33 + const { el1, el2 } = t0;
34 + let s;
35 + if ($[0] !== el1 || $[1] !== el2) {
36 + s = new Set();
37 + let t1;
38 + if ($[3] !== el1) {
39 + t1 = makeArray(el1);
40 + $[3] = el1;
41 + $[4] = t1;
42 + } else {
43 + t1 = $[4];
44 + }
45 + s.add(t1);
46 + let t2;
47 + if ($[5] !== el2) {
48 + t2 = makeArray(el2);
49 + $[5] = el2;
50 + $[6] = t2;
51 + } else {
52 + t2 = $[6];
53 + }
54 + s.add(t2);
55 + $[0] = el1;
56 + $[1] = el2;
57 + $[2] = s;
58 + } else {
59 + s = $[2];
60 + }
61 + return s.size;
62 +}
63 +
64 +export const FIXTURE_ENTRYPOINT = {
65 + fn: useHook,
66 + params: [{ el1: 1, el2: "foo" }],
67 + sequentialRenders: [
68 + { el1: 1, el2: "foo" },
69 + { el1: 2, el2: "foo" },
70 + ],
71 +};
72 +
73 +```
74 +
75 +### Eval output
76 +(kind: ok) 2
77 +2
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor.ts new
+17
@@ -0,0 +1,17 @@
1 +import {makeArray} from 'shared-runtime';
2 +
3 +function useHook({el1, el2}) {
4 + const s = new Set();
5 + s.add(makeArray(el1));
6 + s.add(makeArray(el2));
7 + return s.size;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: useHook,
12 + params: [{el1: 1, el2: 'foo'}],
13 + sequentialRenders: [
14 + {el1: 1, el2: 'foo'},
15 + {el1: 2, el2: 'foo'},
16 + ],
17 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-copy-constructor-mutate.expect.md new
+82
@@ -0,0 +1,82 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {makeArray, mutate} from 'shared-runtime';
6 +
7 +function useFoo({propArr}: {propArr: Array<number>}) {
8 + const s1 = new Set<number | Array<number>>([1, 2, 3]);
9 + s1.add(makeArray(propArr[0]));
10 +
11 + const s2 = new Set(s1);
12 + // this may also may mutate s1
13 + mutate(s2);
14 +
15 + return [s1, s2];
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: useFoo,
20 + params: [{propArr: [7, 8, 9]}],
21 + sequentialRenders: [
22 + {propArr: [7, 8, 9]},
23 + {propArr: [7, 8, 9]},
24 + {propArr: [7, 8, 10]},
25 + ],
26 +};
27 +
28 +```
29 +
30 +## Code
31 +
32 +```javascript
33 +import { c as _c } from "react/compiler-runtime";
34 +import { makeArray, mutate } from "shared-runtime";
35 +
36 +function useFoo(t0) {
37 + const $ = _c(6);
38 + const { propArr } = t0;
39 + let s1;
40 + let s2;
41 + if ($[0] !== propArr[0]) {
42 + s1 = new Set([1, 2, 3]);
43 + s1.add(makeArray(propArr[0]));
44 +
45 + s2 = new Set(s1);
46 +
47 + mutate(s2);
48 + $[0] = propArr[0];
49 + $[1] = s1;
50 + $[2] = s2;
51 + } else {
52 + s1 = $[1];
53 + s2 = $[2];
54 + }
55 + let t1;
56 + if ($[3] !== s1 || $[4] !== s2) {
57 + t1 = [s1, s2];
58 + $[3] = s1;
59 + $[4] = s2;
60 + $[5] = t1;
61 + } else {
62 + t1 = $[5];
63 + }
64 + return t1;
65 +}
66 +
67 +export const FIXTURE_ENTRYPOINT = {
68 + fn: useFoo,
69 + params: [{ propArr: [7, 8, 9] }],
70 + sequentialRenders: [
71 + { propArr: [7, 8, 9] },
72 + { propArr: [7, 8, 9] },
73 + { propArr: [7, 8, 10] },
74 + ],
75 +};
76 +
77 +```
78 +
79 +### Eval output
80 +(kind: ok) [{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
81 +[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
82 +[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-copy-constructor-mutate.ts new
+22
@@ -0,0 +1,22 @@
1 +import {makeArray, mutate} from 'shared-runtime';
2 +
3 +function useFoo({propArr}: {propArr: Array<number>}) {
4 + const s1 = new Set<number | Array<number>>([1, 2, 3]);
5 + s1.add(makeArray(propArr[0]));
6 +
7 + const s2 = new Set(s1);
8 + // this may also may mutate s1
9 + mutate(s2);
10 +
11 + return [s1, s2];
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: useFoo,
16 + params: [{propArr: [7, 8, 9]}],
17 + sequentialRenders: [
18 + {propArr: [7, 8, 9]},
19 + {propArr: [7, 8, 9]},
20 + {propArr: [7, 8, 10]},
21 + ],
22 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-for-of-iterate-values.expect.md new
+65
@@ -0,0 +1,65 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {makeArray, useHook} from 'shared-runtime';
6 +
7 +function useFoo({propArr}: {propArr: Array<number>}) {
8 + const s1 = new Set<number | Array<number>>([1, 2, 3]);
9 + s1.add(makeArray(propArr[0]));
10 +
11 + useHook();
12 + const s2 = new Set();
13 + for (const el of s1.values()) {
14 + s2.add(el);
15 + }
16 +
17 + return [s1, s2];
18 +}
19 +
20 +export const FIXTURE_ENTRYPOINT = {
21 + fn: useFoo,
22 + params: [{propArr: [7, 8, 9]}],
23 + sequentialRenders: [
24 + {propArr: [7, 8, 9]},
25 + {propArr: [7, 8, 9]},
26 + {propArr: [7, 8, 10]},
27 + ],
28 +};
29 +
30 +```
31 +
32 +## Code
33 +
34 +```javascript
35 +import { makeArray, useHook } from "shared-runtime";
36 +
37 +function useFoo(t0) {
38 + const { propArr } = t0;
39 + const s1 = new Set([1, 2, 3]);
40 + s1.add(makeArray(propArr[0]));
41 +
42 + useHook();
43 + const s2 = new Set();
44 + for (const el of s1.values()) {
45 + s2.add(el);
46 + }
47 + return [s1, s2];
48 +}
49 +
50 +export const FIXTURE_ENTRYPOINT = {
51 + fn: useFoo,
52 + params: [{ propArr: [7, 8, 9] }],
53 + sequentialRenders: [
54 + { propArr: [7, 8, 9] },
55 + { propArr: [7, 8, 9] },
56 + { propArr: [7, 8, 10] },
57 + ],
58 +};
59 +
60 +```
61 +
62 +### Eval output
63 +(kind: ok) [{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
64 +[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
65 +[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-for-of-iterate-values.ts new
+24
@@ -0,0 +1,24 @@
1 +import {makeArray, useHook} from 'shared-runtime';
2 +
3 +function useFoo({propArr}: {propArr: Array<number>}) {
4 + const s1 = new Set<number | Array<number>>([1, 2, 3]);
5 + s1.add(makeArray(propArr[0]));
6 +
7 + useHook();
8 + const s2 = new Set();
9 + for (const el of s1.values()) {
10 + s2.add(el);
11 + }
12 +
13 + return [s1, s2];
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: useFoo,
18 + params: [{propArr: [7, 8, 9]}],
19 + sequentialRenders: [
20 + {propArr: [7, 8, 9]},
21 + {propArr: [7, 8, 9]},
22 + {propArr: [7, 8, 10]},
23 + ],
24 +};