From c3ff38349af8dad5dd1ac04527bf45f2373bf1aa Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 21 Aug 2024 09:30:37 -0700 Subject: [PATCH 1/2] [compiler] Type provider infra for tests [ghstack-poisoned] --- .../src/HIR/Environment.ts | 12 +++--- compiler/packages/snap/src/compiler.ts | 2 + .../sprout/shared-runtime-type-provider.ts | 37 +++++++++++++++++++ .../snap/src/sprout/shared-runtime.ts | 8 ++++ 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 58e818205f439..4062817d7f0a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -148,7 +148,7 @@ const EnvironmentConfigSchema = z.object({ * A function that, given the name of a module, can optionally return a description * of that module's type signature. */ - resolveModuleTypeSchema: z.nullable(ModuleTypeResolver).default(null), + moduleTypeProvider: z.nullable(ModuleTypeResolver).default(null), /** * A list of functions which the application compiles as macros, where @@ -713,18 +713,18 @@ export class Environment { } #resolveModuleType(moduleName: string): Global | null { - if (this.config.resolveModuleTypeSchema == null) { + if (this.config.moduleTypeProvider == null) { return null; } let moduleType = this.#moduleTypes.get(moduleName); if (moduleType === undefined) { - const moduleConfig = this.config.resolveModuleTypeSchema(moduleName); - if (moduleConfig != null) { - const moduleTypes = TypeSchema.parse(moduleConfig); + const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName); + if (unparsedModuleConfig != null) { + const moduleConfig = TypeSchema.parse(unparsedModuleConfig); moduleType = installTypeConfig( this.#globals, this.#shapes, - moduleTypes, + moduleConfig, ); } else { moduleType = null; diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index de0184f0bddb1..fb87768b694c4 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -31,6 +31,7 @@ import path from 'path'; import prettier from 'prettier'; import SproutTodoFilter from './SproutTodoFilter'; import {isExpectError} from './fixture-utils'; +import {sharedRuntimeTypeProvider} from './sprout/shared-runtime-type-provider'; export function parseLanguage(source: string): 'flow' | 'typescript' { return source.indexOf('@flow') !== -1 ? 'flow' : 'typescript'; } @@ -241,6 +242,7 @@ function makePluginOptions( }, ], ]), + moduleTypeProvider: sharedRuntimeTypeProvider, customMacros, enableEmitFreeze, enableEmitInstrumentForget, diff --git a/compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts b/compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts new file mode 100644 index 0000000000000..2f7da68cbad79 --- /dev/null +++ b/compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {type TypeConfig} from 'babel-plugin-react-compiler/src/HIR/TypeSchema'; + +export function sharedRuntimeTypeProvider( + moduleName: string, +): TypeConfig | null { + if (moduleName !== './shared-runtime') { + return null; + } + return { + kind: 'object', + properties: { + typedArrayPush: { + kind: 'function', + calleeEffect: 'read', + positionalParams: ['store', 'capture'], + restParam: 'capture', + returnType: {kind: 'type', name: 'Primitive'}, + returnValueKind: 'primitive', + }, + typedLog: { + kind: 'function', + calleeEffect: 'read', + positionalParams: [], + restParam: 'read', + returnType: {kind: 'type', name: 'Primitive'}, + returnValueKind: 'primitive', + }, + }, + }; +} diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index f15aaaaa4a2e5..69af42e52ade3 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -347,3 +347,11 @@ export function useFragment(..._args: Array): object { b: {c: {d: 4}}, }; } + +export function typedArrayPush(array: Array, item: T): void { + array.push(item); +} + +export function typedLog(...values: Array): void { + console.log(...values); +} From 74003b29f129750242b3d9e9d2bf153d14dbe155 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 21 Aug 2024 14:03:50 -0700 Subject: [PATCH 2/2] Update on "[compiler] Type provider infra for tests" [ghstack-poisoned] --- .../src/HIR/Environment.ts | 40 ++-- .../src/HIR/HIR.ts | 9 + .../src/HIR/TypeSchema.ts | 4 +- .../fixtures/compiler/hook-noAlias.expect.md | 38 ++-- ...cal-expression-instruction-scope.expect.md | 19 +- .../compiler/type-provider-log.expect.md | 145 ++++++++++++++ .../fixtures/compiler/type-provider-log.tsx | 29 +++ .../type-provider-store-capture.expect.md | 185 ++++++++++++++++++ .../compiler/type-provider-store-capture.tsx | 35 ++++ compiler/packages/snap/src/compiler.ts | 18 +- compiler/packages/snap/src/constants.ts | 1 + compiler/packages/snap/src/runner-worker.ts | 6 + .../sprout/shared-runtime-type-provider.ts | 61 +++--- 13 files changed, 524 insertions(+), 66 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 4062817d7f0a8..05b2f57baaf3b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -47,6 +47,7 @@ import { } from './ObjectShape'; import {Scope as BabelScope} from '@babel/traverse'; import {TypeSchema} from './TypeSchema'; +import prettyFormat from 'pretty-format'; export const ExternalFunctionSchema = z.object({ // Source for the imported module that exports the `importSpecifierName` functions @@ -126,11 +127,6 @@ const HookSchema = z.object({ export type Hook = z.infer; -export const ModuleTypeResolver = z - .function() - .args(z.string()) - .returns(z.nullable(TypeSchema)); - /* * TODO(mofeiZ): User defined global types (with corresponding shapes). * User defined global types should have inline ObjectShapes instead of directly @@ -148,7 +144,7 @@ const EnvironmentConfigSchema = z.object({ * A function that, given the name of a module, can optionally return a description * of that module's type signature. */ - moduleTypeProvider: z.nullable(ModuleTypeResolver).default(null), + moduleTypeProvider: z.nullable(z.function().args(z.string())).default(null), /** * A list of functions which the application compiles as macros, where @@ -718,15 +714,25 @@ export class Environment { } let moduleType = this.#moduleTypes.get(moduleName); if (moduleType === undefined) { - const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName); - if (unparsedModuleConfig != null) { - const moduleConfig = TypeSchema.parse(unparsedModuleConfig); - moduleType = installTypeConfig( - this.#globals, - this.#shapes, - moduleConfig, - ); - } else { + try { + const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName); + if (unparsedModuleConfig != null) { + const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig); + if (!parsedModuleConfig.success) { + console.error(parsedModuleConfig.error.toString()); + process.exit(1); + } + const moduleConfig = parsedModuleConfig.data; + moduleType = installTypeConfig( + this.#globals, + this.#shapes, + moduleConfig, + ); + } else { + moduleType = null; + } + } catch (e) { + console.error((e as Error).stack); moduleType = null; } this.#moduleTypes.set(moduleName, moduleType); @@ -819,9 +825,7 @@ export class Environment { #isKnownReactModule(moduleName: string): boolean { return ( moduleName.toLowerCase() === 'react' || - moduleName.toLowerCase() === 'react-dom' || - (this.config.enableSharedRuntime__testonly && - moduleName === 'shared-runtime') + moduleName.toLowerCase() === 'react-dom' ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 32a67bd755994..e56c002c513bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1361,6 +1361,15 @@ export enum ValueKind { Context = 'context', } +export const ValueKindSchema = z.enum([ + ValueKind.MaybeFrozen, + ValueKind.Frozen, + ValueKind.Primitive, + ValueKind.Global, + ValueKind.Mutable, + ValueKind.Context, +]); + // The effect with which a value is modified. export enum Effect { // Default value: not allowed after lifetime inference diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts index 66b949da76439..7d865039e04a5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts @@ -8,7 +8,7 @@ import {isValidIdentifier} from '@babel/types'; import {z} from 'zod'; import {Effect, ValueKind} from '..'; -import {EffectSchema} from './HIR'; +import {EffectSchema, ValueKindSchema} from './HIR'; export type ObjectPropertiesConfig = {[key: string]: TypeConfig}; export const ObjectPropertiesSchema: z.ZodType = z @@ -45,7 +45,7 @@ export const FunctionTypeSchema: z.ZodType = z.object({ restParam: EffectSchema.nullable(), calleeEffect: EffectSchema, returnType: z.lazy(() => TypeSchema), - returnValueKind: z.nativeEnum(ValueKind), + returnValueKind: ValueKindSchema, }); export type BuiltInTypeConfig = 'Ref' | 'Array' | 'Primitive' | 'MixedReadonly'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md index 96ccd1e2f1c20..0b54ce1091ffe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md @@ -27,7 +27,7 @@ import { c as _c } from "react/compiler-runtime"; import { useNoAlias } from "shared-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(9); let t0; if ($[0] !== props.a) { t0 = { a: props.a }; @@ -37,19 +37,35 @@ function Component(props) { t0 = $[1]; } const item = t0; - const x = useNoAlias(item, () => { - console.log(props); - }, [props.a]); let t1; - if ($[2] !== x || $[3] !== item) { - t1 = [x, item]; - $[2] = x; - $[3] = item; - $[4] = t1; + if ($[2] !== props) { + t1 = () => { + console.log(props); + }; + $[2] = props; + $[3] = t1; + } else { + t1 = $[3]; + } + let t2; + if ($[4] !== props.a) { + t2 = [props.a]; + $[4] = props.a; + $[5] = t2; + } else { + t2 = $[5]; + } + const x = useNoAlias(item, t1, t2); + let t3; + if ($[6] !== x || $[7] !== item) { + t3 = [x, item]; + $[6] = x; + $[7] = item; + $[8] = t3; } else { - t1 = $[4]; + t3 = $[8]; } - return t1; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/allocating-logical-expression-instruction-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/allocating-logical-expression-instruction-scope.expect.md index 9dee3bb8a705e..e0958981604f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/allocating-logical-expression-instruction-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/allocating-logical-expression-instruction-scope.expect.md @@ -36,16 +36,23 @@ import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR: import { useFragment } from "shared-runtime"; function Foo() { - const $ = _c(2); + const $ = _c(4); const data = useFragment(); - const t0 = data?.toString() || ""; + let t0; + if ($[0] !== data) { + t0 = data?.toString() || ""; + $[0] = data; + $[1] = t0; + } else { + t0 = $[1]; + } let t1; - if ($[0] !== t0) { + if ($[2] !== t0) { t1 = [t0]; - $[0] = t0; - $[1] = t1; + $[2] = t0; + $[3] = t1; } else { - t1 = $[1]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md new file mode 100644 index 0000000000000..072c6d03d9adb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md @@ -0,0 +1,145 @@ + +## Input + +```javascript +import {useMemo} from 'react'; +import {typedLog, ValidateMemoization} from 'shared-runtime'; + +export function Component({a, b}) { + const item1 = useMemo(() => ({a}), [a]); + const item2 = useMemo(() => ({b}), [b]); + typedLog(item1, item2); + + return ( + <> + + + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 0}], + sequentialRenders: [ + {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}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useMemo } from "react"; +import { typedLog, ValidateMemoization } from "shared-runtime"; + +export function Component(t0) { + const $ = _c(17); + const { a, b } = t0; + let t1; + let t2; + if ($[0] !== a) { + t2 = { a }; + $[0] = a; + $[1] = t2; + } else { + t2 = $[1]; + } + t1 = t2; + const item1 = t1; + let t3; + let t4; + if ($[2] !== b) { + t4 = { b }; + $[2] = b; + $[3] = t4; + } else { + t4 = $[3]; + } + t3 = t4; + const item2 = t3; + typedLog(item1, item2); + let t5; + if ($[4] !== a) { + t5 = [a]; + $[4] = a; + $[5] = t5; + } else { + t5 = $[5]; + } + let t6; + if ($[6] !== t5 || $[7] !== item1) { + t6 = ; + $[6] = t5; + $[7] = item1; + $[8] = t6; + } else { + t6 = $[8]; + } + let t7; + if ($[9] !== b) { + t7 = [b]; + $[9] = b; + $[10] = t7; + } else { + t7 = $[10]; + } + let t8; + if ($[11] !== t7 || $[12] !== item2) { + t8 = ; + $[11] = t7; + $[12] = item2; + $[13] = t8; + } else { + t8 = $[13]; + } + let t9; + if ($[14] !== t6 || $[15] !== t8) { + t9 = ( + <> + {t6} + {t8} + + ); + $[14] = t6; + $[15] = t8; + $[16] = t9; + } else { + t9 = $[16]; + } + return t9; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 0, b: 0 }], + sequentialRenders: [ + { 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 }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[0],"output":{"a":0}}
{"inputs":[0],"output":{"b":0}}
+
{"inputs":[1],"output":{"a":1}}
{"inputs":[0],"output":{"b":0}}
+
{"inputs":[1],"output":{"a":1}}
{"inputs":[1],"output":{"b":1}}
+
{"inputs":[1],"output":{"a":1}}
{"inputs":[2],"output":{"b":2}}
+
{"inputs":[2],"output":{"a":2}}
{"inputs":[2],"output":{"b":2}}
+
{"inputs":[3],"output":{"a":3}}
{"inputs":[2],"output":{"b":2}}
+
{"inputs":[0],"output":{"a":0}}
{"inputs":[0],"output":{"b":0}}
+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 diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.tsx new file mode 100644 index 0000000000000..5fb53d9ca85d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.tsx @@ -0,0 +1,29 @@ +import {useMemo} from 'react'; +import {typedLog, ValidateMemoization} from 'shared-runtime'; + +export function Component({a, b}) { + const item1 = useMemo(() => ({a}), [a]); + const item2 = useMemo(() => ({b}), [b]); + typedLog(item1, item2); + + return ( + <> + + + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 0}], + sequentialRenders: [ + {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}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md new file mode 100644 index 0000000000000..a92abd4ca597c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md @@ -0,0 +1,185 @@ + +## Input + +```javascript +import {useMemo} from 'react'; +import {typedArrayPush, ValidateMemoization} from 'shared-runtime'; + +export function Component({a, b}) { + const item1 = useMemo(() => ({a}), [a]); + const item2 = useMemo(() => ({b}), [b]); + const items = useMemo(() => { + const items = []; + typedArrayPush(items, item1); + typedArrayPush(items, item2); + return items; + }, [item1, item2]); + + return ( + <> + + + + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 0}], + sequentialRenders: [ + {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}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useMemo } from "react"; +import { typedArrayPush, ValidateMemoization } from "shared-runtime"; + +export function Component(t0) { + const $ = _c(27); + const { a, b } = t0; + let t1; + let t2; + if ($[0] !== a) { + t2 = { a }; + $[0] = a; + $[1] = t2; + } else { + t2 = $[1]; + } + t1 = t2; + const item1 = t1; + let t3; + let t4; + if ($[2] !== b) { + t4 = { b }; + $[2] = b; + $[3] = t4; + } else { + t4 = $[3]; + } + t3 = t4; + const item2 = t3; + let t5; + let items; + if ($[4] !== item1 || $[5] !== item2) { + items = []; + typedArrayPush(items, item1); + typedArrayPush(items, item2); + $[4] = item1; + $[5] = item2; + $[6] = items; + } else { + items = $[6]; + } + t5 = items; + const items_0 = t5; + let t6; + if ($[7] !== a) { + t6 = [a]; + $[7] = a; + $[8] = t6; + } else { + t6 = $[8]; + } + const t7 = items_0[0]; + let t8; + if ($[9] !== t6 || $[10] !== t7) { + t8 = ; + $[9] = t6; + $[10] = t7; + $[11] = t8; + } else { + t8 = $[11]; + } + let t9; + if ($[12] !== b) { + t9 = [b]; + $[12] = b; + $[13] = t9; + } else { + t9 = $[13]; + } + const t10 = items_0[1]; + let t11; + if ($[14] !== t9 || $[15] !== t10) { + t11 = ; + $[14] = t9; + $[15] = t10; + $[16] = t11; + } else { + t11 = $[16]; + } + let t12; + if ($[17] !== a || $[18] !== b) { + t12 = [a, b]; + $[17] = a; + $[18] = b; + $[19] = t12; + } else { + t12 = $[19]; + } + let t13; + if ($[20] !== t12 || $[21] !== items_0) { + t13 = ; + $[20] = t12; + $[21] = items_0; + $[22] = t13; + } else { + t13 = $[22]; + } + let t14; + if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + t14 = ( + <> + {t8} + {t11} + {t13} + + ); + $[23] = t8; + $[24] = t11; + $[25] = t13; + $[26] = t14; + } else { + t14 = $[26]; + } + return t14; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 0, b: 0 }], + sequentialRenders: [ + { 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 }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[0],"output":{"a":0}}
{"inputs":[0],"output":{"b":0}}
{"inputs":[0,0],"output":[{"a":0},{"b":0}]}
+
{"inputs":[1],"output":{"a":1}}
{"inputs":[0],"output":{"b":0}}
{"inputs":[1,0],"output":[{"a":1},{"b":0}]}
+
{"inputs":[1],"output":{"a":1}}
{"inputs":[1],"output":{"b":1}}
{"inputs":[1,1],"output":[{"a":1},{"b":1}]}
+
{"inputs":[1],"output":{"a":1}}
{"inputs":[2],"output":{"b":2}}
{"inputs":[1,2],"output":[{"a":1},{"b":2}]}
+
{"inputs":[2],"output":{"a":2}}
{"inputs":[2],"output":{"b":2}}
{"inputs":[2,2],"output":[{"a":2},{"b":2}]}
+
{"inputs":[3],"output":{"a":3}}
{"inputs":[2],"output":{"b":2}}
{"inputs":[3,2],"output":[{"a":3},{"b":2}]}
+
{"inputs":[0],"output":{"a":0}}
{"inputs":[0],"output":{"b":0}}
{"inputs":[0,0],"output":[{"a":0},{"b":0}]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.tsx new file mode 100644 index 0000000000000..3afef5439bec3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.tsx @@ -0,0 +1,35 @@ +import {useMemo} from 'react'; +import {typedArrayPush, ValidateMemoization} from 'shared-runtime'; + +export function Component({a, b}) { + const item1 = useMemo(() => ({a}), [a]); + const item2 = useMemo(() => ({b}), [b]); + const items = useMemo(() => { + const items = []; + typedArrayPush(items, item1); + typedArrayPush(items, item2); + return items; + }, [item1, item2]); + + return ( + <> + + + + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 0}], + sequentialRenders: [ + {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}, + ], +}; diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index fb87768b694c4..35e9ca0cb3661 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -31,7 +31,7 @@ import path from 'path'; import prettier from 'prettier'; import SproutTodoFilter from './SproutTodoFilter'; import {isExpectError} from './fixture-utils'; -import {sharedRuntimeTypeProvider} from './sprout/shared-runtime-type-provider'; +import {makeSharedRuntimeTypeProvider} from './sprout/shared-runtime-type-provider'; export function parseLanguage(source: string): 'flow' | 'typescript' { return source.indexOf('@flow') !== -1 ? 'flow' : 'typescript'; } @@ -39,6 +39,8 @@ export function parseLanguage(source: string): 'flow' | 'typescript' { function makePluginOptions( firstLine: string, parseConfigPragmaFn: typeof ParseConfigPragma, + EffectEnum: typeof Effect, + ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { let gating = null; let enableEmitInstrumentForget = null; @@ -242,7 +244,10 @@ function makePluginOptions( }, ], ]), - moduleTypeProvider: sharedRuntimeTypeProvider, + moduleTypeProvider: makeSharedRuntimeTypeProvider({ + EffectEnum, + ValueKindEnum, + }), customMacros, enableEmitFreeze, enableEmitInstrumentForget, @@ -385,6 +390,8 @@ export async function transformFixtureInput( parseConfigPragmaFn: typeof ParseConfigPragma, plugin: BabelCore.PluginObj, includeEvaluator: boolean, + EffectEnum: typeof Effect, + ValueKindEnum: typeof ValueKind, ): Promise<{kind: 'ok'; value: TransformResult} | {kind: 'err'; msg: string}> { // Extract the first line to quickly check for custom test directives const firstLine = input.substring(0, input.indexOf('\n')); @@ -407,7 +414,12 @@ export async function transformFixtureInput( /** * Get Forget compiled code */ - const [options, logs] = makePluginOptions(firstLine, parseConfigPragmaFn); + const [options, logs] = makePluginOptions( + firstLine, + parseConfigPragmaFn, + EffectEnum, + ValueKindEnum, + ); const forgetResult = transformFromAstSync(inputAst, input, { filename: virtualFilepath, highlightCode: false, diff --git a/compiler/packages/snap/src/constants.ts b/compiler/packages/snap/src/constants.ts index bcc0b0ff1ca03..abee06c55be8a 100644 --- a/compiler/packages/snap/src/constants.ts +++ b/compiler/packages/snap/src/constants.ts @@ -17,6 +17,7 @@ export const COMPILER_PATH = path.join( 'Babel', 'BabelPlugin.js', ); +export const COMPILER_INDEX_PATH = path.join(process.cwd(), 'dist', 'index'); export const LOGGER_PATH = path.join( process.cwd(), 'dist', diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index 55c85b9466925..9447b2cddc52c 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -11,6 +11,7 @@ import type {parseConfigPragma as ParseConfigPragma} from 'babel-plugin-react-co import {TransformResult, transformFixtureInput} from './compiler'; import { COMPILER_PATH, + COMPILER_INDEX_PATH, LOGGER_PATH, PARSE_CONFIG_PRAGMA_PATH, } from './constants'; @@ -60,6 +61,9 @@ async function compile( const {default: BabelPluginReactCompiler} = require(COMPILER_PATH) as { default: PluginObj; }; + const {Effect: EffectEnum, ValueKind: ValueKindEnum} = require( + COMPILER_INDEX_PATH, + ); const {toggleLogging} = require(LOGGER_PATH); const {parseConfigPragma} = require(PARSE_CONFIG_PRAGMA_PATH) as { parseConfigPragma: typeof ParseConfigPragma; @@ -74,6 +78,8 @@ async function compile( parseConfigPragma, BabelPluginReactCompiler, includeEvaluator, + EffectEnum, + ValueKindEnum, ); if (result.kind === 'err') { diff --git a/compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts b/compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts index 2f7da68cbad79..d89f734b4a0da 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts @@ -5,33 +5,42 @@ * LICENSE file in the root directory of this source tree. */ -import {type TypeConfig} from 'babel-plugin-react-compiler/src/HIR/TypeSchema'; +import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src'; +import type {TypeConfig} from 'babel-plugin-react-compiler/src/HIR/TypeSchema'; -export function sharedRuntimeTypeProvider( - moduleName: string, -): TypeConfig | null { - if (moduleName !== './shared-runtime') { - return null; - } - return { - kind: 'object', - properties: { - typedArrayPush: { - kind: 'function', - calleeEffect: 'read', - positionalParams: ['store', 'capture'], - restParam: 'capture', - returnType: {kind: 'type', name: 'Primitive'}, - returnValueKind: 'primitive', +export function makeSharedRuntimeTypeProvider({ + EffectEnum, + ValueKindEnum, +}: { + EffectEnum: typeof Effect; + ValueKindEnum: typeof ValueKind; +}) { + return function sharedRuntimeTypeProvider( + moduleName: string, + ): TypeConfig | null { + if (moduleName !== 'shared-runtime') { + return null; + } + return { + kind: 'object', + properties: { + typedArrayPush: { + kind: 'function', + calleeEffect: EffectEnum.Read, + positionalParams: [EffectEnum.Store, EffectEnum.Capture], + restParam: EffectEnum.Capture, + returnType: {kind: 'type', name: 'Primitive'}, + returnValueKind: ValueKindEnum.Primitive, + }, + typedLog: { + kind: 'function', + calleeEffect: EffectEnum.Read, + positionalParams: [], + restParam: EffectEnum.Read, + returnType: {kind: 'type', name: 'Primitive'}, + returnValueKind: ValueKindEnum.Primitive, + }, }, - typedLog: { - kind: 'function', - calleeEffect: 'read', - positionalParams: [], - restParam: 'read', - returnType: {kind: 'type', name: 'Primitive'}, - returnValueKind: 'primitive', - }, - }, + }; }; }