@samitouri / QOS-React / commits / 689c6bd3fd

[compiler][wip] Environment option for resolving imported module types

Adds a new Environment config option which allows specifying a function that is called to resolve types of imported modules. The function is passed the name of the imported module (the RHS of the import stmt) and can return a TypeConfig, which is a recursive type of the following form: * Object of valid identifier keys (or "*" for wildcard) and values that are TypeConfigs * Function with various properties, whose return type is a TypeConfig * or a reference to a builtin type using one of a small list (currently Ref, Array, MixedReadonly, Primitive) Rather than have to eagerly supply all known types (most of which may not be used) when creating the config, this function can do so lazily. During InferTypes we call `getGlobalDeclaration()` to resolve global types. Originally this was just for known react modules, but if the new config option is passed we also call it to see if it can resolve a type. For `import {name} from 'module'` syntax, we first resolve the module type and then call `getPropertyType(moduleType, 'name')` to attempt to retrieve the property of the module (the module would obviously have to be typed as an object type for this to have a chance of yielding a result). If the module type is returned as null, or the property doesn't exist, we fall through to the original checking of whether the name was hook-like. TODO: * testing * cache the results of modules so we don't have to re-parse/install their types on each LoadGlobal of the same module * decide what to do if the module types are invalid. probably better to fatal rather than bail out, since this would indicate an invalid configuration. ghstack-source-id: bfdbf67e3dd0cbfd511bed0bd6ba92266cf99ab8 Pull Request resolved: https://github.com/facebook/react/pull/30771

Joe Savona committed Aug 21, 2024 at 15:45 UTC 689c6bd3fd138ec6c21c54e741da168bdd0c0616
29 files changed +1190 -36
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+67 -4
@@ -17,6 +17,7 @@ import {
17 Global,
18 GlobalRegistry,
19 installReAnimatedTypes,
20 + installTypeConfig,
21 } from './Globals';
22 import {
23 BlockId,
@@ -28,6 +29,7 @@ import {
29 NonLocalBinding,
30 PolyType,
31 ScopeId,
32 + SourceLocation,
33 Type,
34 ValidatedIdentifier,
35 ValueKind,
@@ -45,6 +47,7 @@ import {
47 addHook,
48 } from './ObjectShape';
49 import {Scope as BabelScope} from '@babel/traverse';
50 +import {TypeSchema} from './TypeSchema';
51
52 export const ExternalFunctionSchema = z.object({
53 // Source for the imported module that exports the `importSpecifierName` functions
@@ -137,6 +140,12 @@ export type Hook = z.infer<typeof HookSchema>;
140 const EnvironmentConfigSchema = z.object({
141 customHooks: z.map(z.string(), HookSchema).optional().default(new Map()),
142
143 + /**
144 + * A function that, given the name of a module, can optionally return a description
145 + * of that module's type signature.
146 + */
147 + moduleTypeProvider: z.nullable(z.function().args(z.string())).default(null),
148 +
149 /**
150 * A list of functions which the application compiles as macros, where
151 * the compiler must ensure they are not compiled to rename the macro or separate the
@@ -577,6 +586,7 @@ export function printFunctionType(type: ReactFunctionType): string {
586 export class Environment {
587 #globals: GlobalRegistry;
588 #shapes: ShapeRegistry;
589 + #moduleTypes: Map<string, Global | null> = new Map();
590 #nextIdentifer: number = 0;
591 #nextBlock: number = 0;
592 #nextScope: number = 0;
@@ -698,7 +708,40 @@ export class Environment {
708 return this.#outlinedFunctions;
709 }
710
701 - getGlobalDeclaration(binding: NonLocalBinding): Global | null {
711 + #resolveModuleType(moduleName: string, loc: SourceLocation): Global | null {
712 + if (this.config.moduleTypeProvider == null) {
713 + return null;
714 + }
715 + let moduleType = this.#moduleTypes.get(moduleName);
716 + if (moduleType === undefined) {
717 + const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName);
718 + if (unparsedModuleConfig != null) {
719 + const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig);
720 + if (!parsedModuleConfig.success) {
721 + CompilerError.throwInvalidConfig({
722 + reason: `Could not parse module type, the configured \`moduleTypeProvider\` function returned an invalid module description`,
723 + description: parsedModuleConfig.error.toString(),
724 + loc,
725 + });
726 + }
727 + const moduleConfig = parsedModuleConfig.data;
728 + moduleType = installTypeConfig(
729 + this.#globals,
730 + this.#shapes,
731 + moduleConfig,
732 + );
733 + } else {
734 + moduleType = null;
735 + }
736 + this.#moduleTypes.set(moduleName, moduleType);
737 + }
738 + return moduleType;
739 + }
740 +
741 + getGlobalDeclaration(
742 + binding: NonLocalBinding,
743 + loc: SourceLocation,
744 + ): Global | null {
745 if (this.config.hookPattern != null) {
746 const match = new RegExp(this.config.hookPattern).exec(binding.name);
747 if (
@@ -736,6 +779,17 @@ export class Environment {
779 (isHookName(binding.imported) ? this.#getCustomHookType() : null)
780 );
781 } else {
782 + const moduleType = this.#resolveModuleType(binding.module, loc);
783 + if (moduleType !== null) {
784 + const importedType = this.getPropertyType(
785 + moduleType,
786 + binding.imported,
787 + );
788 + if (importedType != null) {
789 + return importedType;
790 + }
791 + }
792 +
793 /**
794 * For modules we don't own, we look at whether the original name or import alias
795 * are hook-like. Both of the following are likely hooks so we would return a hook
@@ -758,6 +812,17 @@ export class Environment {
812 (isHookName(binding.name) ? this.#getCustomHookType() : null)
813 );
814 } else {
815 + const moduleType = this.#resolveModuleType(binding.module, loc);
816 + if (moduleType !== null) {
817 + if (binding.kind === 'ImportDefault') {
818 + const defaultType = this.getPropertyType(moduleType, 'default');
819 + if (defaultType !== null) {
820 + return defaultType;
821 + }
822 + } else {
823 + return moduleType;
824 + }
825 + }
826 return isHookName(binding.name) ? this.#getCustomHookType() : null;
827 }
828 }
@@ -767,9 +832,7 @@ export class Environment {
832 #isKnownReactModule(moduleName: string): boolean {
833 return (
834 moduleName.toLowerCase() === 'react' ||
770 - moduleName.toLowerCase() === 'react-dom' ||
771 - (this.config.enableSharedRuntime__testonly &&
772 - moduleName === 'shared-runtime')
835 + moduleName.toLowerCase() === 'react-dom'
836 );
837 }
838
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
+76
@@ -9,6 +9,7 @@ import {Effect, ValueKind, ValueReason} from './HIR';
9 import {
10 BUILTIN_SHAPES,
11 BuiltInArrayId,
12 + BuiltInMixedReadonlyId,
13 BuiltInUseActionStateId,
14 BuiltInUseContextHookId,
15 BuiltInUseEffectHookId,
@@ -25,6 +26,8 @@ import {
26 addObject,
27 } from './ObjectShape';
28 import {BuiltInType, PolyType} from './Types';
29 +import {TypeConfig} from './TypeSchema';
30 +import {assertExhaustive} from '../Utils/utils';
31
32 /*
33 * This file exports types and defaults for JavaScript global objects.
@@ -528,6 +531,79 @@ DEFAULT_GLOBALS.set(
531 addObject(DEFAULT_SHAPES, 'global', TYPED_GLOBALS),
532 );
533
534 +export function installTypeConfig(
535 + globals: GlobalRegistry,
536 + shapes: ShapeRegistry,
537 + typeConfig: TypeConfig,
538 +): Global {
539 + switch (typeConfig.kind) {
540 + case 'type': {
541 + switch (typeConfig.name) {
542 + case 'Array': {
543 + return {kind: 'Object', shapeId: BuiltInArrayId};
544 + }
545 + case 'MixedReadonly': {
546 + return {kind: 'Object', shapeId: BuiltInMixedReadonlyId};
547 + }
548 + case 'Primitive': {
549 + return {kind: 'Primitive'};
550 + }
551 + case 'Ref': {
552 + return {kind: 'Object', shapeId: BuiltInUseRefId};
553 + }
554 + case 'Any': {
555 + return {kind: 'Poly'};
556 + }
557 + default: {
558 + assertExhaustive(
559 + typeConfig.name,
560 + `Unexpected type '${(typeConfig as any).name}'`,
561 + );
562 + }
563 + }
564 + }
565 + case 'function': {
566 + return addFunction(shapes, [], {
567 + positionalParams: typeConfig.positionalParams,
568 + restParam: typeConfig.restParam,
569 + calleeEffect: typeConfig.calleeEffect,
570 + returnType: installTypeConfig(globals, shapes, typeConfig.returnType),
571 + returnValueKind: typeConfig.returnValueKind,
572 + noAlias: typeConfig.noAlias === true,
573 + mutableOnlyIfOperandsAreMutable:
574 + typeConfig.mutableOnlyIfOperandsAreMutable === true,
575 + });
576 + }
577 + case 'hook': {
578 + return addHook(shapes, {
579 + hookKind: 'Custom',
580 + positionalParams: typeConfig.positionalParams ?? [],
581 + restParam: typeConfig.restParam ?? Effect.Freeze,
582 + calleeEffect: Effect.Read,
583 + returnType: installTypeConfig(globals, shapes, typeConfig.returnType),
584 + returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
585 + noAlias: typeConfig.noAlias === true,
586 + });
587 + }
588 + case 'object': {
589 + return addObject(
590 + shapes,
591 + null,
592 + Object.entries(typeConfig.properties ?? {}).map(([key, value]) => [
593 + key,
594 + installTypeConfig(globals, shapes, value),
595 + ]),
596 + );
597 + }
598 + default: {
599 + assertExhaustive(
600 + typeConfig,
601 + `Unexpected type kind '${(typeConfig as any).kind}'`,
602 + );
603 + }
604 + }
605 +}
606 +
607 export function installReAnimatedTypes(
608 globals: GlobalRegistry,
609 registry: ShapeRegistry,
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+19
@@ -12,6 +12,7 @@ import {assertExhaustive} from '../Utils/utils';
12 import {Environment, ReactFunctionType} from './Environment';
13 import {HookKind} from './ObjectShape';
14 import {Type, makeType} from './Types';
15 +import {z} from 'zod';
16
17 /*
18 * *******************************************************************************************
@@ -1360,6 +1361,15 @@ export enum ValueKind {
1361 Context = 'context',
1362 }
1363
1364 +export const ValueKindSchema = z.enum([
1365 + ValueKind.MaybeFrozen,
1366 + ValueKind.Frozen,
1367 + ValueKind.Primitive,
1368 + ValueKind.Global,
1369 + ValueKind.Mutable,
1370 + ValueKind.Context,
1371 +]);
1372 +
1373 // The effect with which a value is modified.
1374 export enum Effect {
1375 // Default value: not allowed after lifetime inference
@@ -1389,6 +1399,15 @@ export enum Effect {
1399 Store = 'store',
1400 }
1401
1402 +export const EffectSchema = z.enum([
1403 + Effect.Read,
1404 + Effect.Mutate,
1405 + Effect.ConditionallyMutate,
1406 + Effect.Capture,
1407 + Effect.Store,
1408 + Effect.Freeze,
1409 +]);
1410 +
1411 export function isMutableEffect(
1412 effect: Effect,
1413 location: SourceLocation,
compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts new
+105
@@ -0,0 +1,105 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import {isValidIdentifier} from '@babel/types';
9 +import {z} from 'zod';
10 +import {Effect, ValueKind} from '..';
11 +import {EffectSchema, ValueKindSchema} from './HIR';
12 +
13 +export type ObjectPropertiesConfig = {[key: string]: TypeConfig};
14 +export const ObjectPropertiesSchema: z.ZodType<ObjectPropertiesConfig> = z
15 + .record(
16 + z.string(),
17 + z.lazy(() => TypeSchema),
18 + )
19 + .refine(record => {
20 + return Object.keys(record).every(
21 + key => key === '*' || key === 'default' || isValidIdentifier(key),
22 + );
23 + }, 'Expected all "object" property names to be valid identifier, `*` to match any property, of `default` to define a module default export');
24 +
25 +export type ObjectTypeConfig = {
26 + kind: 'object';
27 + properties: ObjectPropertiesConfig | null;
28 +};
29 +export const ObjectTypeSchema: z.ZodType<ObjectTypeConfig> = z.object({
30 + kind: z.literal('object'),
31 + properties: ObjectPropertiesSchema.nullable(),
32 +});
33 +
34 +export type FunctionTypeConfig = {
35 + kind: 'function';
36 + positionalParams: Array<Effect>;
37 + restParam: Effect | null;
38 + calleeEffect: Effect;
39 + returnType: TypeConfig;
40 + returnValueKind: ValueKind;
41 + noAlias?: boolean | null | undefined;
42 + mutableOnlyIfOperandsAreMutable?: boolean | null | undefined;
43 +};
44 +export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
45 + kind: z.literal('function'),
46 + positionalParams: z.array(EffectSchema),
47 + restParam: EffectSchema.nullable(),
48 + calleeEffect: EffectSchema,
49 + returnType: z.lazy(() => TypeSchema),
50 + returnValueKind: ValueKindSchema,
51 + noAlias: z.boolean().nullable().optional(),
52 + mutableOnlyIfOperandsAreMutable: z.boolean().nullable().optional(),
53 +});
54 +
55 +export type HookTypeConfig = {
56 + kind: 'hook';
57 + positionalParams?: Array<Effect> | null | undefined;
58 + restParam?: Effect | null | undefined;
59 + returnType: TypeConfig;
60 + returnValueKind?: ValueKind | null | undefined;
61 + noAlias?: boolean | null | undefined;
62 +};
63 +export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
64 + kind: z.literal('hook'),
65 + positionalParams: z.array(EffectSchema).nullable().optional(),
66 + restParam: EffectSchema.nullable().optional(),
67 + returnType: z.lazy(() => TypeSchema),
68 + returnValueKind: ValueKindSchema.nullable().optional(),
69 + noAlias: z.boolean().nullable().optional(),
70 +});
71 +
72 +export type BuiltInTypeConfig =
73 + | 'Any'
74 + | 'Ref'
75 + | 'Array'
76 + | 'Primitive'
77 + | 'MixedReadonly';
78 +export const BuiltInTypeSchema: z.ZodType<BuiltInTypeConfig> = z.union([
79 + z.literal('Any'),
80 + z.literal('Ref'),
81 + z.literal('Array'),
82 + z.literal('Primitive'),
83 + z.literal('MixedReadonly'),
84 +]);
85 +
86 +export type TypeReferenceConfig = {
87 + kind: 'type';
88 + name: BuiltInTypeConfig;
89 +};
90 +export const TypeReferenceSchema: z.ZodType<TypeReferenceConfig> = z.object({
91 + kind: z.literal('type'),
92 + name: BuiltInTypeSchema,
93 +});
94 +
95 +export type TypeConfig =
96 + | ObjectTypeConfig
97 + | FunctionTypeConfig
98 + | HookTypeConfig
99 + | TypeReferenceConfig;
100 +export const TypeSchema: z.ZodType<TypeConfig> = z.union([
101 + ObjectTypeSchema,
102 + FunctionTypeSchema,
103 + HookTypeSchema,
104 + TypeReferenceSchema,
105 +]);
compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts
+1 -1
@@ -127,7 +127,7 @@ function collectTemporaries(
127 break;
128 }
129 case 'LoadGlobal': {
130 - const global = env.getGlobalDeclaration(value.binding);
130 + const global = env.getGlobalDeclaration(value.binding, value.loc);
131 const hookKind = global !== null ? getHookKindForType(env, global) : null;
132 const lvalId = instr.lvalue.identifier.id;
133 if (hookKind === 'useMemo' || hookKind === 'useCallback') {
compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts
+1 -1
@@ -227,7 +227,7 @@ function* generateInstructionTypes(
227 }
228
229 case 'LoadGlobal': {
230 - const globalType = env.getGlobalDeclaration(value.binding);
230 + const globalType = env.getGlobalDeclaration(value.binding, value.loc);
231 if (globalType) {
232 yield equation(left, globalType);
233 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md
+4
@@ -2,6 +2,8 @@
2 ## Input
3
4 ```javascript
5 +import {useFragment} from 'shared-runtime';
6 +
7 function Component(props) {
8 const post = useFragment(
9 graphql`
@@ -36,6 +38,8 @@ function Component(props) {
38
39 ```javascript
40 import { c as _c } from "react/compiler-runtime";
41 +import { useFragment } from "shared-runtime";
42 +
43 function Component(props) {
44 const $ = _c(4);
45 const post = useFragment(
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.js
+2
@@ -1,3 +1,5 @@
1 +import {useFragment} from 'shared-runtime';
2 +
3 function Component(props) {
4 const post = useFragment(
5 graphql`
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-logical.expect.md
+4
@@ -2,6 +2,8 @@
2 ## Input
3
4 ```javascript
5 +import {useFragment} from 'shared-runtime';
6 +
7 function Component(props) {
8 const item = useFragment(
9 graphql`
@@ -20,6 +22,8 @@ function Component(props) {
22
23 ```javascript
24 import { c as _c } from "react/compiler-runtime";
25 +import { useFragment } from "shared-runtime";
26 +
27 function Component(props) {
28 const $ = _c(2);
29 const item = useFragment(
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-logical.js
+2
@@ -1,3 +1,5 @@
1 +import {useFragment} from 'shared-runtime';
2 +
3 function Component(props) {
4 const item = useFragment(
5 graphql`
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/readonly-object-method-calls-mutable-lambda.expect.md
+4
@@ -2,6 +2,8 @@
2 ## Input
3
4 ```javascript
5 +import {useFragment} from 'shared-runtime';
6 +
7 function Component(props) {
8 const x = makeObject();
9 const user = useFragment(
@@ -28,6 +30,8 @@ function Component(props) {
30
31 ```javascript
32 import { c as _c } from "react/compiler-runtime";
33 +import { useFragment } from "shared-runtime";
34 +
35 function Component(props) {
36 const $ = _c(3);
37 const x = makeObject();
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/readonly-object-method-calls-mutable-lambda.js
+2
@@ -1,3 +1,5 @@
1 +import {useFragment} from 'shared-runtime';
2 +
3 function Component(props) {
4 const x = makeObject();
5 const user = useFragment(
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/readonly-object-method-calls.expect.md
+4
@@ -2,6 +2,8 @@
2 ## Input
3
4 ```javascript
5 +import {useFragment} from 'shared-runtime';
6 +
7 function Component(props) {
8 const user = useFragment(
9 graphql`
@@ -26,6 +28,8 @@ function Component(props) {
28
29 ```javascript
30 import { c as _c } from "react/compiler-runtime";
31 +import { useFragment } from "shared-runtime";
32 +
33 function Component(props) {
34 const $ = _c(5);
35 const user = useFragment(
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/readonly-object-method-calls.js
+2
@@ -1,3 +1,5 @@
1 +import {useFragment} from 'shared-runtime';
2 +
3 function Component(props) {
4 const user = useFragment(
5 graphql`
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/tagged-template-in-hook.expect.md
+4
@@ -2,6 +2,8 @@
2 ## Input
3
4 ```javascript
5 +import {useFragment} from 'shared-runtime';
6 +
7 function Component(props) {
8 const user = useFragment(
9 graphql`
@@ -19,6 +21,8 @@ function Component(props) {
21 ## Code
22
23 ```javascript
24 +import { useFragment } from "shared-runtime";
25 +
26 function Component(props) {
27 const user = useFragment(
28 graphql`
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/tagged-template-in-hook.js
+2
@@ -1,3 +1,5 @@
1 +import {useFragment} from 'shared-runtime';
2 +
3 function Component(props) {
4 const user = useFragment(
5 graphql`
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md new
+147
@@ -0,0 +1,147 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {useMemo} from 'react';
6 +import {ValidateMemoization} from 'shared-runtime';
7 +import typedLog from 'shared-runtime';
8 +
9 +export function Component({a, b}) {
10 + const item1 = useMemo(() => ({a}), [a]);
11 + const item2 = useMemo(() => ({b}), [b]);
12 + typedLog(item1, item2);
13 +
14 + return (
15 + <>
16 + <ValidateMemoization inputs={[a]} output={item1} />
17 + <ValidateMemoization inputs={[b]} output={item2} />
18 + </>
19 + );
20 +}
21 +
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: Component,
24 + params: [{a: 0, b: 0}],
25 + sequentialRenders: [
26 + {a: 0, b: 0},
27 + {a: 1, b: 0},
28 + {a: 1, b: 1},
29 + {a: 1, b: 2},
30 + {a: 2, b: 2},
31 + {a: 3, b: 2},
32 + {a: 0, b: 0},
33 + ],
34 +};
35 +
36 +```
37 +
38 +## Code
39 +
40 +```javascript
41 +import { c as _c } from "react/compiler-runtime";
42 +import { useMemo } from "react";
43 +import { ValidateMemoization } from "shared-runtime";
44 +import typedLog from "shared-runtime";
45 +
46 +export function Component(t0) {
47 + const $ = _c(17);
48 + const { a, b } = t0;
49 + let t1;
50 + let t2;
51 + if ($[0] !== a) {
52 + t2 = { a };
53 + $[0] = a;
54 + $[1] = t2;
55 + } else {
56 + t2 = $[1];
57 + }
58 + t1 = t2;
59 + const item1 = t1;
60 + let t3;
61 + let t4;
62 + if ($[2] !== b) {
63 + t4 = { b };
64 + $[2] = b;
65 + $[3] = t4;
66 + } else {
67 + t4 = $[3];
68 + }
69 + t3 = t4;
70 + const item2 = t3;
71 + typedLog(item1, item2);
72 + let t5;
73 + if ($[4] !== a) {
74 + t5 = [a];
75 + $[4] = a;
76 + $[5] = t5;
77 + } else {
78 + t5 = $[5];
79 + }
80 + let t6;
81 + if ($[6] !== t5 || $[7] !== item1) {
82 + t6 = <ValidateMemoization inputs={t5} output={item1} />;
83 + $[6] = t5;
84 + $[7] = item1;
85 + $[8] = t6;
86 + } else {
87 + t6 = $[8];
88 + }
89 + let t7;
90 + if ($[9] !== b) {
91 + t7 = [b];
92 + $[9] = b;
93 + $[10] = t7;
94 + } else {
95 + t7 = $[10];
96 + }
97 + let t8;
98 + if ($[11] !== t7 || $[12] !== item2) {
99 + t8 = <ValidateMemoization inputs={t7} output={item2} />;
100 + $[11] = t7;
101 + $[12] = item2;
102 + $[13] = t8;
103 + } else {
104 + t8 = $[13];
105 + }
106 + let t9;
107 + if ($[14] !== t6 || $[15] !== t8) {
108 + t9 = (
109 + <>
110 + {t6}
111 + {t8}
112 + </>
113 + );
114 + $[14] = t6;
115 + $[15] = t8;
116 + $[16] = t9;
117 + } else {
118 + t9 = $[16];
119 + }
120 + return t9;
121 +}
122 +
123 +export const FIXTURE_ENTRYPOINT = {
124 + fn: Component,
125 + params: [{ a: 0, b: 0 }],
126 + sequentialRenders: [
127 + { a: 0, b: 0 },
128 + { a: 1, b: 0 },
129 + { a: 1, b: 1 },
130 + { a: 1, b: 2 },
131 + { a: 2, b: 2 },
132 + { a: 3, b: 2 },
133 + { a: 0, b: 0 },
134 + ],
135 +};
136 +
137 +```
138 +
139 +### Eval output
140 +(kind: ok) <div>{"inputs":[0],"output":{"a":0}}</div><div>{"inputs":[0],"output":{"b":0}}</div>
141 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[0],"output":{"b":0}}</div>
142 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[1],"output":{"b":1}}</div>
143 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[2],"output":{"b":2}}</div>
144 +<div>{"inputs":[2],"output":{"a":2}}</div><div>{"inputs":[2],"output":{"b":2}}</div>
145 +<div>{"inputs":[3],"output":{"a":3}}</div><div>{"inputs":[2],"output":{"b":2}}</div>
146 +<div>{"inputs":[0],"output":{"a":0}}</div><div>{"inputs":[0],"output":{"b":0}}</div>
147 +logs: [{ a: 0 },{ b: 0 },{ a: 1 },{ b: 0 },{ a: 1 },{ b: 1 },{ a: 1 },{ b: 2 },{ a: 2 },{ b: 2 },{ a: 3 },{ b: 2 },{ a: 0 },{ b: 0 }]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.tsx new
+30
@@ -0,0 +1,30 @@
1 +import {useMemo} from 'react';
2 +import {ValidateMemoization} from 'shared-runtime';
3 +import typedLog from 'shared-runtime';
4 +
5 +export function Component({a, b}) {
6 + const item1 = useMemo(() => ({a}), [a]);
7 + const item2 = useMemo(() => ({b}), [b]);
8 + typedLog(item1, item2);
9 +
10 + return (
11 + <>
12 + <ValidateMemoization inputs={[a]} output={item1} />
13 + <ValidateMemoization inputs={[b]} output={item2} />
14 + </>
15 + );
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: Component,
20 + params: [{a: 0, b: 0}],
21 + sequentialRenders: [
22 + {a: 0, b: 0},
23 + {a: 1, b: 0},
24 + {a: 1, b: 1},
25 + {a: 1, b: 2},
26 + {a: 2, b: 2},
27 + {a: 3, b: 2},
28 + {a: 0, b: 0},
29 + ],
30 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md new
+145
@@ -0,0 +1,145 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {useMemo} from 'react';
6 +import {typedLog, ValidateMemoization} from 'shared-runtime';
7 +
8 +export function Component({a, b}) {
9 + const item1 = useMemo(() => ({a}), [a]);
10 + const item2 = useMemo(() => ({b}), [b]);
11 + typedLog(item1, item2);
12 +
13 + return (
14 + <>
15 + <ValidateMemoization inputs={[a]} output={item1} />
16 + <ValidateMemoization inputs={[b]} output={item2} />
17 + </>
18 + );
19 +}
20 +
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: Component,
23 + params: [{a: 0, b: 0}],
24 + sequentialRenders: [
25 + {a: 0, b: 0},
26 + {a: 1, b: 0},
27 + {a: 1, b: 1},
28 + {a: 1, b: 2},
29 + {a: 2, b: 2},
30 + {a: 3, b: 2},
31 + {a: 0, b: 0},
32 + ],
33 +};
34 +
35 +```
36 +
37 +## Code
38 +
39 +```javascript
40 +import { c as _c } from "react/compiler-runtime";
41 +import { useMemo } from "react";
42 +import { typedLog, ValidateMemoization } from "shared-runtime";
43 +
44 +export function Component(t0) {
45 + const $ = _c(17);
46 + const { a, b } = t0;
47 + let t1;
48 + let t2;
49 + if ($[0] !== a) {
50 + t2 = { a };
51 + $[0] = a;
52 + $[1] = t2;
53 + } else {
54 + t2 = $[1];
55 + }
56 + t1 = t2;
57 + const item1 = t1;
58 + let t3;
59 + let t4;
60 + if ($[2] !== b) {
61 + t4 = { b };
62 + $[2] = b;
63 + $[3] = t4;
64 + } else {
65 + t4 = $[3];
66 + }
67 + t3 = t4;
68 + const item2 = t3;
69 + typedLog(item1, item2);
70 + let t5;
71 + if ($[4] !== a) {
72 + t5 = [a];
73 + $[4] = a;
74 + $[5] = t5;
75 + } else {
76 + t5 = $[5];
77 + }
78 + let t6;
79 + if ($[6] !== t5 || $[7] !== item1) {
80 + t6 = <ValidateMemoization inputs={t5} output={item1} />;
81 + $[6] = t5;
82 + $[7] = item1;
83 + $[8] = t6;
84 + } else {
85 + t6 = $[8];
86 + }
87 + let t7;
88 + if ($[9] !== b) {
89 + t7 = [b];
90 + $[9] = b;
91 + $[10] = t7;
92 + } else {
93 + t7 = $[10];
94 + }
95 + let t8;
96 + if ($[11] !== t7 || $[12] !== item2) {
97 + t8 = <ValidateMemoization inputs={t7} output={item2} />;
98 + $[11] = t7;
99 + $[12] = item2;
100 + $[13] = t8;
101 + } else {
102 + t8 = $[13];
103 + }
104 + let t9;
105 + if ($[14] !== t6 || $[15] !== t8) {
106 + t9 = (
107 + <>
108 + {t6}
109 + {t8}
110 + </>
111 + );
112 + $[14] = t6;
113 + $[15] = t8;
114 + $[16] = t9;
115 + } else {
116 + t9 = $[16];
117 + }
118 + return t9;
119 +}
120 +
121 +export const FIXTURE_ENTRYPOINT = {
122 + fn: Component,
123 + params: [{ a: 0, b: 0 }],
124 + sequentialRenders: [
125 + { a: 0, b: 0 },
126 + { a: 1, b: 0 },
127 + { a: 1, b: 1 },
128 + { a: 1, b: 2 },
129 + { a: 2, b: 2 },
130 + { a: 3, b: 2 },
131 + { a: 0, b: 0 },
132 + ],
133 +};
134 +
135 +```
136 +
137 +### Eval output
138 +(kind: ok) <div>{"inputs":[0],"output":{"a":0}}</div><div>{"inputs":[0],"output":{"b":0}}</div>
139 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[0],"output":{"b":0}}</div>
140 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[1],"output":{"b":1}}</div>
141 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[2],"output":{"b":2}}</div>
142 +<div>{"inputs":[2],"output":{"a":2}}</div><div>{"inputs":[2],"output":{"b":2}}</div>
143 +<div>{"inputs":[3],"output":{"a":3}}</div><div>{"inputs":[2],"output":{"b":2}}</div>
144 +<div>{"inputs":[0],"output":{"a":0}}</div><div>{"inputs":[0],"output":{"b":0}}</div>
145 +logs: [{ a: 0 },{ b: 0 },{ a: 1 },{ b: 0 },{ a: 1 },{ b: 1 },{ a: 1 },{ b: 2 },{ a: 2 },{ b: 2 },{ a: 3 },{ b: 2 },{ a: 0 },{ b: 0 }]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.tsx new
+29
@@ -0,0 +1,29 @@
1 +import {useMemo} from 'react';
2 +import {typedLog, ValidateMemoization} from 'shared-runtime';
3 +
4 +export function Component({a, b}) {
5 + const item1 = useMemo(() => ({a}), [a]);
6 + const item2 = useMemo(() => ({b}), [b]);
7 + typedLog(item1, item2);
8 +
9 + return (
10 + <>
11 + <ValidateMemoization inputs={[a]} output={item1} />
12 + <ValidateMemoization inputs={[b]} output={item2} />
13 + </>
14 + );
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{a: 0, b: 0}],
20 + sequentialRenders: [
21 + {a: 0, b: 0},
22 + {a: 1, b: 0},
23 + {a: 1, b: 1},
24 + {a: 1, b: 2},
25 + {a: 2, b: 2},
26 + {a: 3, b: 2},
27 + {a: 0, b: 0},
28 + ],
29 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md new
+185
@@ -0,0 +1,185 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {useMemo} from 'react';
6 +import * as SharedRuntime from 'shared-runtime';
7 +
8 +export function Component({a, b}) {
9 + const item1 = useMemo(() => ({a}), [a]);
10 + const item2 = useMemo(() => ({b}), [b]);
11 + const items = useMemo(() => {
12 + const items = [];
13 + SharedRuntime.typedArrayPush(items, item1);
14 + SharedRuntime.typedArrayPush(items, item2);
15 + return items;
16 + }, [item1, item2]);
17 +
18 + return (
19 + <>
20 + <SharedRuntime.ValidateMemoization inputs={[a]} output={items[0]} />
21 + <SharedRuntime.ValidateMemoization inputs={[b]} output={items[1]} />
22 + <SharedRuntime.ValidateMemoization inputs={[a, b]} output={items} />
23 + </>
24 + );
25 +}
26 +
27 +export const FIXTURE_ENTRYPOINT = {
28 + fn: Component,
29 + params: [{a: 0, b: 0}],
30 + sequentialRenders: [
31 + {a: 0, b: 0},
32 + {a: 1, b: 0},
33 + {a: 1, b: 1},
34 + {a: 1, b: 2},
35 + {a: 2, b: 2},
36 + {a: 3, b: 2},
37 + {a: 0, b: 0},
38 + ],
39 +};
40 +
41 +```
42 +
43 +## Code
44 +
45 +```javascript
46 +import { c as _c } from "react/compiler-runtime";
47 +import { useMemo } from "react";
48 +import * as SharedRuntime from "shared-runtime";
49 +
50 +export function Component(t0) {
51 + const $ = _c(27);
52 + const { a, b } = t0;
53 + let t1;
54 + let t2;
55 + if ($[0] !== a) {
56 + t2 = { a };
57 + $[0] = a;
58 + $[1] = t2;
59 + } else {
60 + t2 = $[1];
61 + }
62 + t1 = t2;
63 + const item1 = t1;
64 + let t3;
65 + let t4;
66 + if ($[2] !== b) {
67 + t4 = { b };
68 + $[2] = b;
69 + $[3] = t4;
70 + } else {
71 + t4 = $[3];
72 + }
73 + t3 = t4;
74 + const item2 = t3;
75 + let t5;
76 + let items;
77 + if ($[4] !== item1 || $[5] !== item2) {
78 + items = [];
79 + SharedRuntime.typedArrayPush(items, item1);
80 + SharedRuntime.typedArrayPush(items, item2);
81 + $[4] = item1;
82 + $[5] = item2;
83 + $[6] = items;
84 + } else {
85 + items = $[6];
86 + }
87 + t5 = items;
88 + const items_0 = t5;
89 + let t6;
90 + if ($[7] !== a) {
91 + t6 = [a];
92 + $[7] = a;
93 + $[8] = t6;
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;
104 + } else {
105 + t8 = $[11];
106 + }
107 + let t9;
108 + if ($[12] !== b) {
109 + t9 = [b];
110 + $[12] = b;
111 + $[13] = t9;
112 + } else {
113 + t9 = $[13];
114 + }
115 + const t10 = items_0[1];
116 + let t11;
117 + if ($[14] !== t9 || $[15] !== t10) {
118 + t11 = <SharedRuntime.ValidateMemoization inputs={t9} output={t10} />;
119 + $[14] = t9;
120 + $[15] = t10;
121 + $[16] = t11;
122 + } else {
123 + t11 = $[16];
124 + }
125 + let t12;
126 + if ($[17] !== a || $[18] !== b) {
127 + t12 = [a, b];
128 + $[17] = a;
129 + $[18] = b;
130 + $[19] = t12;
131 + } else {
132 + t12 = $[19];
133 + }
134 + let t13;
135 + if ($[20] !== t12 || $[21] !== items_0) {
136 + t13 = <SharedRuntime.ValidateMemoization inputs={t12} output={items_0} />;
137 + $[20] = t12;
138 + $[21] = items_0;
139 + $[22] = t13;
140 + } else {
141 + t13 = $[22];
142 + }
143 + let t14;
144 + if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) {
145 + t14 = (
146 + <>
147 + {t8}
148 + {t11}
149 + {t13}
150 + </>
151 + );
152 + $[23] = t8;
153 + $[24] = t11;
154 + $[25] = t13;
155 + $[26] = t14;
156 + } else {
157 + t14 = $[26];
158 + }
159 + return t14;
160 +}
161 +
162 +export const FIXTURE_ENTRYPOINT = {
163 + fn: Component,
164 + params: [{ a: 0, b: 0 }],
165 + sequentialRenders: [
166 + { a: 0, b: 0 },
167 + { a: 1, b: 0 },
168 + { a: 1, b: 1 },
169 + { a: 1, b: 2 },
170 + { a: 2, b: 2 },
171 + { a: 3, b: 2 },
172 + { a: 0, b: 0 },
173 + ],
174 +};
175 +
176 +```
177 +
178 +### Eval output
179 +(kind: ok) <div>{"inputs":[0],"output":{"a":0}}</div><div>{"inputs":[0],"output":{"b":0}}</div><div>{"inputs":[0,0],"output":[{"a":0},{"b":0}]}</div>
180 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[0],"output":{"b":0}}</div><div>{"inputs":[1,0],"output":[{"a":1},{"b":0}]}</div>
181 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[1],"output":{"b":1}}</div><div>{"inputs":[1,1],"output":[{"a":1},{"b":1}]}</div>
182 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[2],"output":{"b":2}}</div><div>{"inputs":[1,2],"output":[{"a":1},{"b":2}]}</div>
183 +<div>{"inputs":[2],"output":{"a":2}}</div><div>{"inputs":[2],"output":{"b":2}}</div><div>{"inputs":[2,2],"output":[{"a":2},{"b":2}]}</div>
184 +<div>{"inputs":[3],"output":{"a":3}}</div><div>{"inputs":[2],"output":{"b":2}}</div><div>{"inputs":[3,2],"output":[{"a":3},{"b":2}]}</div>
185 +<div>{"inputs":[0],"output":{"a":0}}</div><div>{"inputs":[0],"output":{"b":0}}</div><div>{"inputs":[0,0],"output":[{"a":0},{"b":0}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.tsx new
+35
@@ -0,0 +1,35 @@
1 +import {useMemo} from 'react';
2 +import * as SharedRuntime from 'shared-runtime';
3 +
4 +export function Component({a, b}) {
5 + const item1 = useMemo(() => ({a}), [a]);
6 + const item2 = useMemo(() => ({b}), [b]);
7 + const items = useMemo(() => {
8 + const items = [];
9 + SharedRuntime.typedArrayPush(items, item1);
10 + SharedRuntime.typedArrayPush(items, item2);
11 + return items;
12 + }, [item1, item2]);
13 +
14 + return (
15 + <>
16 + <SharedRuntime.ValidateMemoization inputs={[a]} output={items[0]} />
17 + <SharedRuntime.ValidateMemoization inputs={[b]} output={items[1]} />
18 + <SharedRuntime.ValidateMemoization inputs={[a, b]} output={items} />
19 + </>
20 + );
21 +}
22 +
23 +export const FIXTURE_ENTRYPOINT = {
24 + fn: Component,
25 + params: [{a: 0, b: 0}],
26 + sequentialRenders: [
27 + {a: 0, b: 0},
28 + {a: 1, b: 0},
29 + {a: 1, b: 1},
30 + {a: 1, b: 2},
31 + {a: 2, b: 2},
32 + {a: 3, b: 2},
33 + {a: 0, b: 0},
34 + ],
35 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md new
+185
@@ -0,0 +1,185 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {useMemo} from 'react';
6 +import {typedArrayPush, ValidateMemoization} from 'shared-runtime';
7 +
8 +export function Component({a, b}) {
9 + const item1 = useMemo(() => ({a}), [a]);
10 + const item2 = useMemo(() => ({b}), [b]);
11 + const items = useMemo(() => {
12 + const items = [];
13 + typedArrayPush(items, item1);
14 + typedArrayPush(items, item2);
15 + return items;
16 + }, [item1, item2]);
17 +
18 + return (
19 + <>
20 + <ValidateMemoization inputs={[a]} output={items[0]} />
21 + <ValidateMemoization inputs={[b]} output={items[1]} />
22 + <ValidateMemoization inputs={[a, b]} output={items} />
23 + </>
24 + );
25 +}
26 +
27 +export const FIXTURE_ENTRYPOINT = {
28 + fn: Component,
29 + params: [{a: 0, b: 0}],
30 + sequentialRenders: [
31 + {a: 0, b: 0},
32 + {a: 1, b: 0},
33 + {a: 1, b: 1},
34 + {a: 1, b: 2},
35 + {a: 2, b: 2},
36 + {a: 3, b: 2},
37 + {a: 0, b: 0},
38 + ],
39 +};
40 +
41 +```
42 +
43 +## Code
44 +
45 +```javascript
46 +import { c as _c } from "react/compiler-runtime";
47 +import { useMemo } from "react";
48 +import { typedArrayPush, ValidateMemoization } from "shared-runtime";
49 +
50 +export function Component(t0) {
51 + const $ = _c(27);
52 + const { a, b } = t0;
53 + let t1;
54 + let t2;
55 + if ($[0] !== a) {
56 + t2 = { a };
57 + $[0] = a;
58 + $[1] = t2;
59 + } else {
60 + t2 = $[1];
61 + }
62 + t1 = t2;
63 + const item1 = t1;
64 + let t3;
65 + let t4;
66 + if ($[2] !== b) {
67 + t4 = { b };
68 + $[2] = b;
69 + $[3] = t4;
70 + } else {
71 + t4 = $[3];
72 + }
73 + t3 = t4;
74 + const item2 = t3;
75 + let t5;
76 + let items;
77 + if ($[4] !== item1 || $[5] !== item2) {
78 + items = [];
79 + typedArrayPush(items, item1);
80 + typedArrayPush(items, item2);
81 + $[4] = item1;
82 + $[5] = item2;
83 + $[6] = items;
84 + } else {
85 + items = $[6];
86 + }
87 + t5 = items;
88 + const items_0 = t5;
89 + let t6;
90 + if ($[7] !== a) {
91 + t6 = [a];
92 + $[7] = a;
93 + $[8] = t6;
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;
104 + } else {
105 + t8 = $[11];
106 + }
107 + let t9;
108 + if ($[12] !== b) {
109 + t9 = [b];
110 + $[12] = b;
111 + $[13] = t9;
112 + } else {
113 + t9 = $[13];
114 + }
115 + const t10 = items_0[1];
116 + let t11;
117 + if ($[14] !== t9 || $[15] !== t10) {
118 + t11 = <ValidateMemoization inputs={t9} output={t10} />;
119 + $[14] = t9;
120 + $[15] = t10;
121 + $[16] = t11;
122 + } else {
123 + t11 = $[16];
124 + }
125 + let t12;
126 + if ($[17] !== a || $[18] !== b) {
127 + t12 = [a, b];
128 + $[17] = a;
129 + $[18] = b;
130 + $[19] = t12;
131 + } else {
132 + t12 = $[19];
133 + }
134 + let t13;
135 + if ($[20] !== t12 || $[21] !== items_0) {
136 + t13 = <ValidateMemoization inputs={t12} output={items_0} />;
137 + $[20] = t12;
138 + $[21] = items_0;
139 + $[22] = t13;
140 + } else {
141 + t13 = $[22];
142 + }
143 + let t14;
144 + if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) {
145 + t14 = (
146 + <>
147 + {t8}
148 + {t11}
149 + {t13}
150 + </>
151 + );
152 + $[23] = t8;
153 + $[24] = t11;
154 + $[25] = t13;
155 + $[26] = t14;
156 + } else {
157 + t14 = $[26];
158 + }
159 + return t14;
160 +}
161 +
162 +export const FIXTURE_ENTRYPOINT = {
163 + fn: Component,
164 + params: [{ a: 0, b: 0 }],
165 + sequentialRenders: [
166 + { a: 0, b: 0 },
167 + { a: 1, b: 0 },
168 + { a: 1, b: 1 },
169 + { a: 1, b: 2 },
170 + { a: 2, b: 2 },
171 + { a: 3, b: 2 },
172 + { a: 0, b: 0 },
173 + ],
174 +};
175 +
176 +```
177 +
178 +### Eval output
179 +(kind: ok) <div>{"inputs":[0],"output":{"a":0}}</div><div>{"inputs":[0],"output":{"b":0}}</div><div>{"inputs":[0,0],"output":[{"a":0},{"b":0}]}</div>
180 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[0],"output":{"b":0}}</div><div>{"inputs":[1,0],"output":[{"a":1},{"b":0}]}</div>
181 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[1],"output":{"b":1}}</div><div>{"inputs":[1,1],"output":[{"a":1},{"b":1}]}</div>
182 +<div>{"inputs":[1],"output":{"a":1}}</div><div>{"inputs":[2],"output":{"b":2}}</div><div>{"inputs":[1,2],"output":[{"a":1},{"b":2}]}</div>
183 +<div>{"inputs":[2],"output":{"a":2}}</div><div>{"inputs":[2],"output":{"b":2}}</div><div>{"inputs":[2,2],"output":[{"a":2},{"b":2}]}</div>
184 +<div>{"inputs":[3],"output":{"a":3}}</div><div>{"inputs":[2],"output":{"b":2}}</div><div>{"inputs":[3,2],"output":[{"a":3},{"b":2}]}</div>
185 +<div>{"inputs":[0],"output":{"a":0}}</div><div>{"inputs":[0],"output":{"b":0}}</div><div>{"inputs":[0,0],"output":[{"a":0},{"b":0}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.tsx new
+35
@@ -0,0 +1,35 @@
1 +import {useMemo} from 'react';
2 +import {typedArrayPush, ValidateMemoization} from 'shared-runtime';
3 +
4 +export function Component({a, b}) {
5 + const item1 = useMemo(() => ({a}), [a]);
6 + const item2 = useMemo(() => ({b}), [b]);
7 + const items = useMemo(() => {
8 + const items = [];
9 + typedArrayPush(items, item1);
10 + typedArrayPush(items, item2);
11 + return items;
12 + }, [item1, item2]);
13 +
14 + return (
15 + <>
16 + <ValidateMemoization inputs={[a]} output={items[0]} />
17 + <ValidateMemoization inputs={[b]} output={items[1]} />
18 + <ValidateMemoization inputs={[a, b]} output={items} />
19 + </>
20 + );
21 +}
22 +
23 +export const FIXTURE_ENTRYPOINT = {
24 + fn: Component,
25 + params: [{a: 0, b: 0}],
26 + sequentialRenders: [
27 + {a: 0, b: 0},
28 + {a: 1, b: 0},
29 + {a: 1, b: 1},
30 + {a: 1, b: 2},
31 + {a: 2, b: 2},
32 + {a: 3, b: 2},
33 + {a: 0, b: 0},
34 + ],
35 +};
compiler/packages/snap/src/compiler.ts
+15 -30
@@ -31,6 +31,7 @@ import path from 'path';
31 import prettier from 'prettier';
32 import SproutTodoFilter from './SproutTodoFilter';
33 import {isExpectError} from './fixture-utils';
34 +import {makeSharedRuntimeTypeProvider} from './sprout/shared-runtime-type-provider';
35 export function parseLanguage(source: string): 'flow' | 'typescript' {
36 return source.indexOf('@flow') !== -1 ? 'flow' : 'typescript';
37 }
@@ -38,6 +39,8 @@ export function parseLanguage(source: string): 'flow' | 'typescript' {
39 function makePluginOptions(
40 firstLine: string,
41 parseConfigPragmaFn: typeof ParseConfigPragma,
42 + EffectEnum: typeof Effect,
43 + ValueKindEnum: typeof ValueKind,
44 ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] {
45 let gating = null;
46 let enableEmitInstrumentForget = null;
@@ -212,35 +215,10 @@ function makePluginOptions(
215 const options = {
216 environment: {
217 ...config,
215 - customHooks: new Map([
216 - [
217 - 'useFreeze',
218 - {
219 - valueKind: 'frozen' as ValueKind,
220 - effectKind: 'freeze' as Effect,
221 - transitiveMixedData: false,
222 - noAlias: false,
223 - },
224 - ],
225 - [
226 - 'useFragment',
227 - {
228 - valueKind: 'frozen' as ValueKind,
229 - effectKind: 'freeze' as Effect,
230 - transitiveMixedData: true,
231 - noAlias: true,
232 - },
233 - ],
234 - [
235 - 'useNoAlias',
236 - {
237 - valueKind: 'mutable' as ValueKind,
238 - effectKind: 'read' as Effect,
239 - transitiveMixedData: false,
240 - noAlias: true,
241 - },
242 - ],
243 - ]),
218 + moduleTypeProvider: makeSharedRuntimeTypeProvider({
219 + EffectEnum,
220 + ValueKindEnum,
221 + }),
222 customMacros,
223 enableEmitFreeze,
224 enableEmitInstrumentForget,
@@ -383,6 +361,8 @@ export async function transformFixtureInput(
361 parseConfigPragmaFn: typeof ParseConfigPragma,
362 plugin: BabelCore.PluginObj,
363 includeEvaluator: boolean,
364 + EffectEnum: typeof Effect,
365 + ValueKindEnum: typeof ValueKind,
366 ): Promise<{kind: 'ok'; value: TransformResult} | {kind: 'err'; msg: string}> {
367 // Extract the first line to quickly check for custom test directives
368 const firstLine = input.substring(0, input.indexOf('\n'));
@@ -405,7 +385,12 @@ export async function transformFixtureInput(
385 /**
386 * Get Forget compiled code
387 */
408 - const [options, logs] = makePluginOptions(firstLine, parseConfigPragmaFn);
388 + const [options, logs] = makePluginOptions(
389 + firstLine,
390 + parseConfigPragmaFn,
391 + EffectEnum,
392 + ValueKindEnum,
393 + );
394 const forgetResult = transformFromAstSync(inputAst, input, {
395 filename: virtualFilepath,
396 highlightCode: false,
compiler/packages/snap/src/constants.ts
+1
@@ -17,6 +17,7 @@ export const COMPILER_PATH = path.join(
17 'Babel',
18 'BabelPlugin.js',
19 );
20 +export const COMPILER_INDEX_PATH = path.join(process.cwd(), 'dist', 'index');
21 export const LOGGER_PATH = path.join(
22 process.cwd(),
23 'dist',
compiler/packages/snap/src/runner-worker.ts
+6
@@ -11,6 +11,7 @@ import type {parseConfigPragma as ParseConfigPragma} from 'babel-plugin-react-co
11 import {TransformResult, transformFixtureInput} from './compiler';
12 import {
13 COMPILER_PATH,
14 + COMPILER_INDEX_PATH,
15 LOGGER_PATH,
16 PARSE_CONFIG_PRAGMA_PATH,
17 } from './constants';
@@ -60,6 +61,9 @@ async function compile(
61 const {default: BabelPluginReactCompiler} = require(COMPILER_PATH) as {
62 default: PluginObj;
63 };
64 + const {Effect: EffectEnum, ValueKind: ValueKindEnum} = require(
65 + COMPILER_INDEX_PATH,
66 + );
67 const {toggleLogging} = require(LOGGER_PATH);
68 const {parseConfigPragma} = require(PARSE_CONFIG_PRAGMA_PATH) as {
69 parseConfigPragma: typeof ParseConfigPragma;
@@ -74,6 +78,8 @@ async function compile(
78 parseConfigPragma,
79 BabelPluginReactCompiler,
80 includeEvaluator,
81 + EffectEnum,
82 + ValueKindEnum,
83 );
84
85 if (result.kind === 'err') {
compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts new
+69
@@ -0,0 +1,69 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src';
9 +import type {TypeConfig} from 'babel-plugin-react-compiler/src/HIR/TypeSchema';
10 +
11 +export function makeSharedRuntimeTypeProvider({
12 + EffectEnum,
13 + ValueKindEnum,
14 +}: {
15 + EffectEnum: typeof Effect;
16 + ValueKindEnum: typeof ValueKind;
17 +}) {
18 + return function sharedRuntimeTypeProvider(
19 + moduleName: string,
20 + ): TypeConfig | null {
21 + if (moduleName !== 'shared-runtime') {
22 + return null;
23 + }
24 + return {
25 + kind: 'object',
26 + properties: {
27 + default: {
28 + kind: 'function',
29 + calleeEffect: EffectEnum.Read,
30 + positionalParams: [],
31 + restParam: EffectEnum.Read,
32 + returnType: {kind: 'type', name: 'Primitive'},
33 + returnValueKind: ValueKindEnum.Primitive,
34 + },
35 + typedArrayPush: {
36 + kind: 'function',
37 + calleeEffect: EffectEnum.Read,
38 + positionalParams: [EffectEnum.Store, EffectEnum.Capture],
39 + restParam: EffectEnum.Capture,
40 + returnType: {kind: 'type', name: 'Primitive'},
41 + returnValueKind: ValueKindEnum.Primitive,
42 + },
43 + typedLog: {
44 + kind: 'function',
45 + calleeEffect: EffectEnum.Read,
46 + positionalParams: [],
47 + restParam: EffectEnum.Read,
48 + returnType: {kind: 'type', name: 'Primitive'},
49 + returnValueKind: ValueKindEnum.Primitive,
50 + },
51 + useFreeze: {
52 + kind: 'hook',
53 + returnType: {kind: 'type', name: 'Any'},
54 + },
55 + useFragment: {
56 + kind: 'hook',
57 + returnType: {kind: 'type', name: 'MixedReadonly'},
58 + noAlias: true,
59 + },
60 + useNoAlias: {
61 + kind: 'hook',
62 + returnType: {kind: 'type', name: 'Any'},
63 + returnValueKind: ValueKindEnum.Mutable,
64 + noAlias: true,
65 + },
66 + },
67 + };
68 + };
69 +}
compiler/packages/snap/src/sprout/shared-runtime.ts
+9
@@ -347,3 +347,12 @@ export function useFragment(..._args: Array<any>): object {
347 b: {c: {d: 4}},
348 };
349 }
350 +
351 +export function typedArrayPush<T>(array: Array<T>, item: T): void {
352 + array.push(item);
353 +}
354 +
355 +export function typedLog(...values: Array<any>): void {
356 + console.log(...values);
357 +}
358 +export default typedLog;