[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
+};