diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 8307e8817b4f9..2f6d8a94021ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -46,18 +46,13 @@ import {instructionReordering} from '../Optimization/InstructionReordering'; import { CodegenFunction, alignObjectMethodScopes, - alignReactiveScopesToBlockScopes, assertScopeInstructionsWithinScopes, assertWellFormedBreakTargets, - buildReactiveBlocks, buildReactiveFunction, codegenFunction, extractScopeDeclarationsFromDestructuring, - flattenReactiveLoops, - flattenScopesWithHooksOrUse, inferReactiveScopeVariables, memoizeFbtAndMacroOperandsInSameScope, - mergeOverlappingReactiveScopes, mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, @@ -300,54 +295,52 @@ function* runWithEnvironment( value: hir, }); - if (env.config.enableReactiveScopesInHIR) { - pruneUnusedLabelsHIR(hir); - yield log({ - kind: 'hir', - name: 'PruneUnusedLabelsHIR', - value: hir, - }); + pruneUnusedLabelsHIR(hir); + yield log({ + kind: 'hir', + name: 'PruneUnusedLabelsHIR', + value: hir, + }); - alignReactiveScopesToBlockScopesHIR(hir); - yield log({ - kind: 'hir', - name: 'AlignReactiveScopesToBlockScopesHIR', - value: hir, - }); + alignReactiveScopesToBlockScopesHIR(hir); + yield log({ + kind: 'hir', + name: 'AlignReactiveScopesToBlockScopesHIR', + value: hir, + }); - mergeOverlappingReactiveScopesHIR(hir); - yield log({ - kind: 'hir', - name: 'MergeOverlappingReactiveScopesHIR', - value: hir, - }); - assertValidBlockNesting(hir); + mergeOverlappingReactiveScopesHIR(hir); + yield log({ + kind: 'hir', + name: 'MergeOverlappingReactiveScopesHIR', + value: hir, + }); + assertValidBlockNesting(hir); - buildReactiveScopeTerminalsHIR(hir); - yield log({ - kind: 'hir', - name: 'BuildReactiveScopeTerminalsHIR', - value: hir, - }); + buildReactiveScopeTerminalsHIR(hir); + yield log({ + kind: 'hir', + name: 'BuildReactiveScopeTerminalsHIR', + value: hir, + }); - assertValidBlockNesting(hir); + assertValidBlockNesting(hir); - flattenReactiveLoopsHIR(hir); - yield log({ - kind: 'hir', - name: 'FlattenReactiveLoopsHIR', - value: hir, - }); + flattenReactiveLoopsHIR(hir); + yield log({ + kind: 'hir', + name: 'FlattenReactiveLoopsHIR', + value: hir, + }); - flattenScopesWithHooksOrUseHIR(hir); - yield log({ - kind: 'hir', - name: 'FlattenScopesWithHooksOrUseHIR', - value: hir, - }); - assertTerminalSuccessorsExist(hir); - assertTerminalPredsExist(hir); - } + flattenScopesWithHooksOrUseHIR(hir); + yield log({ + kind: 'hir', + name: 'FlattenScopesWithHooksOrUseHIR', + value: hir, + }); + assertTerminalSuccessorsExist(hir); + assertTerminalPredsExist(hir); const reactiveFunction = buildReactiveFunction(hir); yield log({ @@ -364,44 +357,6 @@ function* runWithEnvironment( name: 'PruneUnusedLabels', value: reactiveFunction, }); - - if (!env.config.enableReactiveScopesInHIR) { - alignReactiveScopesToBlockScopes(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'AlignReactiveScopesToBlockScopes', - value: reactiveFunction, - }); - - mergeOverlappingReactiveScopes(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'MergeOverlappingReactiveScopes', - value: reactiveFunction, - }); - - buildReactiveBlocks(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'BuildReactiveBlocks', - value: reactiveFunction, - }); - - flattenReactiveLoops(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'FlattenReactiveLoops', - value: reactiveFunction, - }); - - flattenScopesWithHooksOrUse(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'FlattenScopesWithHooks', - value: reactiveFunction, - }); - } - assertScopeInstructionsWithinScopes(reactiveFunction); propagateScopeDependencies(reactiveFunction); 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 2b007f9659b99..ccce274847fbe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -222,8 +222,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enableReactiveScopesInHIR: z.boolean().default(true), - /** * Enables inference of optional dependency chains. Without this flag * a property chain such as `props?.items?.foo` will infer as a dep on 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 3a04a4c3c9dce..930dd79f2fd59 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1492,6 +1492,7 @@ export type ReactiveScopeDeclaration = { scope: ReactiveScope; // the scope in which the variable was originally declared }; +export type DependencyPath = Array<{property: string; optional: boolean}>; export type ReactiveScopeDependency = { identifier: Identifier; path: DependencyPath; @@ -1506,7 +1507,21 @@ export function areEqualPaths(a: DependencyPath, b: DependencyPath): boolean { ) ); } -export type DependencyPath = Array<{property: string; optional: boolean}>; + +export function getPlaceScope( + id: InstructionId, + place: Place, +): ReactiveScope | null { + const scope = place.identifier.scope; + if (scope !== null && isScopeActive(scope, id)) { + return scope; + } + return null; +} + +function isScopeActive(scope: ReactiveScope, id: InstructionId): boolean { + return id >= scope.range.start && id < scope.range.end; +} /* * Simulated opaque type for BlockIds to prevent using normal numbers as block ids diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts index 98645d5c549af..a3740539b295b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts @@ -5,7 +5,7 @@ import { ReactiveScope, makeInstructionId, } from '.'; -import {getPlaceScope} from '../ReactiveScopes/BuildReactiveBlocks'; +import {getPlaceScope} from '../HIR/HIR'; import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables'; import DisjointSet from '../Utils/DisjointSet'; import {getOrInsertDefault} from '../Utils/utils'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopes.ts deleted file mode 100644 index 132788f0d418b..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopes.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * 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 { - InstructionId, - Place, - ReactiveBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveScope, - ScopeId, - makeInstructionId, -} from '../HIR/HIR'; -import {getPlaceScope} from './BuildReactiveBlocks'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Note: this is the 2nd of 4 passes that determine how to break a function into discrete - * reactive scopes (independently memoizeable units of code): - * 1. InferReactiveScopeVariables (on HIR) determines operands that mutate together and assigns - * them a unique reactive scope. - * 2. AlignReactiveScopesToBlockScopes (this pass, on ReactiveFunction) aligns reactive scopes - * to block scopes. - * 3. MergeOverlappingReactiveScopes (on ReactiveFunction) ensures that reactive scopes do not - * overlap, merging any such scopes. - * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into - * a ReactiveScopeBlock. - * - * Prior inference passes assign a reactive scope to each operand, but the ranges of these - * scopes are based on specific instructions at arbitrary points in the control-flow graph. - * However, to codegen blocks around the instructions in each scope, the scopes must be - * aligned to block-scope boundaries - we can't memoize half of a loop! - * - * This pass updates reactive scope boundaries to align to control flow boundaries, for - * example: - * - * ```javascript - * function foo(cond, a) { - * ⌵ original scope - * ⌵ expanded scope - * const x = []; ⌝ ⌝ - * if (cond) { ⎮ ⎮ - * ... ⎮ ⎮ - * x.push(a); ⌟ ⎮ - * ... ⎮ - * } ⌟ - * } - * ``` - * - * Here the original scope for `x` ended partway through the if consequent, but we can't - * memoize part of that block. This pass would align the scope to the end of the consequent. - * - * The more general rule is that a reactive scope may only end at the same block scope as it - * began: this pass therefore finds, for each scope, the block where that scope started and - * finds the first instruction after the scope's mutable range in that same block scope (which - * will be the updated end for that scope). - */ - -export function alignReactiveScopesToBlockScopes(fn: ReactiveFunction): void { - const context = new Context(); - visitReactiveFunction(fn, new Visitor(), context); -} - -class Visitor extends ReactiveFunctionVisitor { - override visitID(id: InstructionId, state: Context): void { - state.visitId(id); - } - override visitPlace(id: InstructionId, place: Place, state: Context): void { - const scope = getPlaceScope(id, place); - if (scope !== null) { - state.visitScope(scope); - } - } - override visitLValue(id: InstructionId, lvalue: Place, state: Context): void { - const scope = getPlaceScope(id, lvalue); - if (scope !== null) { - state.visitScope(scope); - } - } - - override visitInstruction(instr: ReactiveInstruction, state: Context): void { - switch (instr.value.kind) { - case 'OptionalExpression': - case 'SequenceExpression': - case 'ConditionalExpression': - case 'LogicalExpression': { - const prevScopeCount = state.currentScopes().length; - this.traverseInstruction(instr, state); - - /** - * These compound value types can have nested sequences of instructions - * with scopes that start "partway" through a block-level instruction. - * This would cause the start of the scope to not align with any block-level - * instruction and get skipped by the later BuildReactiveBlocks pass. - * - * Here we detect scopes created within compound instructions and align the - * start of these scopes to the outer instruction id to ensure the scopes - * aren't skipped. - */ - const scopes = state.currentScopes(); - for (let i = prevScopeCount; i < scopes.length; i++) { - const scope = scopes[i]; - scope.scope.range.start = makeInstructionId( - Math.min(instr.id, scope.scope.range.start), - ); - } - break; - } - default: { - this.traverseInstruction(instr, state); - } - } - } - - override visitBlock(block: ReactiveBlock, state: Context): void { - state.enter(() => { - this.traverseBlock(block, state); - }); - } -} - -type PendingReactiveScope = {active: boolean; scope: ReactiveScope}; - -class Context { - /* - * For each block scope (outer array) stores a list of ReactiveScopes that start - * in that block scope. - */ - #blockScopes: Array> = []; - - /* - * ReactiveScopes whose declaring block scope has ended but may still need to - * be "closed" (ie have their range.end be updated). A given scope can be in - * blockScopes OR this array but not both. - */ - #unclosedScopes: Array = []; - - /* - * Set of all scope ids that have been seen so far, regardless of which of - * the above data structures they're in, to avoid tracking the same scope twice. - */ - #seenScopes: Set = new Set(); - - currentScopes(): Array { - return this.#blockScopes.at(-1) ?? []; - } - - enter(fn: () => void): void { - this.#blockScopes.push([]); - fn(); - const lastScope = this.#blockScopes.pop()!; - for (const scope of lastScope) { - if (scope.active) { - this.#unclosedScopes.push(scope); - } - } - } - - visitId(id: InstructionId): void { - const currentScopes = this.#blockScopes.at(-1)!; - const scopes = [...currentScopes, ...this.#unclosedScopes]; - for (const pending of scopes) { - if (!pending.active) { - continue; - } - if (id >= pending.scope.range.end) { - pending.active = false; - pending.scope.range.end = id; - } - } - } - - visitScope(scope: ReactiveScope): void { - if (!this.#seenScopes.has(scope.id)) { - const currentScopes = this.#blockScopes.at(-1)!; - this.#seenScopes.add(scope.id); - currentScopes.push({ - active: true, - scope, - }); - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts index 3e8329679cfe2..2b4e890a40da8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts @@ -13,6 +13,7 @@ import { MutableRange, Place, ReactiveScope, + getPlaceScope, makeInstructionId, } from '../HIR/HIR'; import { @@ -23,7 +24,6 @@ import { terminalFallthrough, } from '../HIR/visitors'; import {retainWhere_Set} from '../Utils/utils'; -import {getPlaceScope} from './BuildReactiveBlocks'; type InstructionRange = MutableRange; /* diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts index 2bce100050175..718e28f6101d1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts @@ -14,7 +14,7 @@ import { ReactiveScopeBlock, ScopeId, } from '../HIR'; -import {getPlaceScope} from './BuildReactiveBlocks'; +import {getPlaceScope} from '../HIR/HIR'; import {ReactiveFunctionVisitor} from './visitors'; /* diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveBlocks.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveBlocks.ts deleted file mode 100644 index 7737423e5e1c1..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveBlocks.ts +++ /dev/null @@ -1,244 +0,0 @@ -/** - * 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 {CompilerError} from '../CompilerError'; -import { - BlockId, - InstructionId, - Place, - ReactiveBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveScope, - ReactiveScopeBlock, - ReactiveStatement, - ScopeId, -} from '../HIR'; -import {eachInstructionLValue} from '../HIR/visitors'; -import {assertExhaustive} from '../Utils/utils'; -import {eachReactiveValueOperand, mapTerminalBlocks} from './visitors'; - -/* - * Note: this is the 4th of 4 passes that determine how to break a function into discrete - * reactive scopes (independently memoizeable units of code): - * 1. InferReactiveScopeVariables (on HIR) determines operands that mutate together and assigns - * them a unique reactive scope. - * 2. AlignReactiveScopesToBlockScopes (on ReactiveFunction) aligns reactive scopes - * to block scopes. - * 3. MergeOverlappingReactiveScopes (this pass, on ReactiveFunction) ensures that reactive - * scopes do not overlap, merging any such scopes. - * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into - * a ReactiveScopeBlock. - * - * Given a function where the reactive scopes have been correctly aligned and merged, - * this pass groups the instructions for each reactive scope into ReactiveBlocks. - */ -export function buildReactiveBlocks(fn: ReactiveFunction): void { - const context = new Context(); - fn.body = context.enter(() => { - visitBlock(context, fn.body); - }); -} - -class Context { - #builders: Array = []; - #scopes: Set = new Set(); - - visitId(id: InstructionId): void { - const builder = this.#builders.at(-1)!; - builder.visitId(id); - } - - visitScope(scope: ReactiveScope): void { - if (this.#scopes.has(scope.id)) { - return; - } - this.#scopes.add(scope.id); - this.#builders.at(-1)!.startScope(scope); - } - - append( - stmt: ReactiveStatement, - label: {id: BlockId; implicit: boolean} | null, - ): void { - this.#builders.at(-1)!.append(stmt, label); - } - - enter(fn: () => void): ReactiveBlock { - const builder = new Builder(); - this.#builders.push(builder); - fn(); - const popped = this.#builders.pop(); - CompilerError.invariant(popped === builder, { - reason: 'Expected push/pop to be called 1:1', - description: null, - loc: null, - suggestions: null, - }); - return builder.complete(); - } -} - -class Builder { - #instructions: ReactiveBlock; - #stack: Array< - | {kind: 'scope'; block: ReactiveScopeBlock} - | {kind: 'block'; block: ReactiveBlock} - >; - - constructor() { - const block: ReactiveBlock = []; - this.#instructions = block; - this.#stack = [{kind: 'block', block}]; - } - - append( - item: ReactiveStatement, - label: {id: BlockId; implicit: boolean} | null, - ): void { - if (label !== null) { - CompilerError.invariant(item.kind === 'terminal', { - reason: 'Only terminals may have a label', - description: null, - loc: null, - suggestions: null, - }); - item.label = label; - } - this.#instructions.push(item); - } - - startScope(scope: ReactiveScope): void { - const block: ReactiveScopeBlock = { - kind: 'scope', - scope, - instructions: [], - }; - this.append(block, null); - this.#instructions = block.instructions; - this.#stack.push({kind: 'scope', block}); - } - - visitId(id: InstructionId): void { - for (let i = 0; i < this.#stack.length; i++) { - const entry = this.#stack[i]!; - if (entry.kind === 'scope' && id >= entry.block.scope.range.end) { - this.#stack.length = i; - break; - } - } - const last = this.#stack[this.#stack.length - 1]!; - if (last.kind === 'block') { - this.#instructions = last.block; - } else { - this.#instructions = last.block.instructions; - } - } - - complete(): ReactiveBlock { - /* - * TODO: @josephsavona debug violations of this invariant - * invariant( - * this.#stack.length === 1, - * "Expected all scopes to be closed when exiting a block" - * ); - */ - const first = this.#stack[0]!; - CompilerError.invariant(first.kind === 'block', { - reason: 'Expected first stack item to be a basic block', - description: null, - loc: null, - suggestions: null, - }); - return first.block; - } -} - -function visitBlock(context: Context, block: ReactiveBlock): void { - for (const stmt of block) { - switch (stmt.kind) { - case 'instruction': { - context.visitId(stmt.instruction.id); - const scope = getInstructionScope(stmt.instruction); - if (scope !== null) { - context.visitScope(scope); - } - context.append(stmt, null); - break; - } - case 'terminal': { - const id = stmt.terminal.id; - if (id !== null) { - context.visitId(id); - } - mapTerminalBlocks(stmt.terminal, block => { - return context.enter(() => { - visitBlock(context, block); - }); - }); - context.append(stmt, stmt.label); - break; - } - case 'pruned-scope': - case 'scope': { - CompilerError.invariant(false, { - reason: 'Expected the function to not have scopes already assigned', - description: null, - loc: null, - suggestions: null, - }); - } - default: { - assertExhaustive( - stmt, - `Unexpected statement kind \`${(stmt as any).kind}\``, - ); - } - } - } -} - -export function getInstructionScope( - instr: ReactiveInstruction, -): ReactiveScope | null { - CompilerError.invariant(instr.lvalue !== null, { - reason: - 'Expected lvalues to not be null when assigning scopes. ' + - 'Pruning lvalues too early can result in missing scope information.', - description: null, - loc: instr.loc, - suggestions: null, - }); - for (const operand of eachInstructionLValue(instr)) { - const operandScope = getPlaceScope(instr.id, operand); - if (operandScope !== null) { - return operandScope; - } - } - for (const operand of eachReactiveValueOperand(instr.value)) { - const operandScope = getPlaceScope(instr.id, operand); - if (operandScope !== null) { - return operandScope; - } - } - return null; -} - -export function getPlaceScope( - id: InstructionId, - place: Place, -): ReactiveScope | null { - const scope = place.identifier.scope; - if (scope !== null && isScopeActive(scope, id)) { - return scope; - } - return null; -} - -function isScopeActive(scope: ReactiveScope, id: InstructionId): boolean { - return id >= scope.range.start && id < scope.range.end; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenReactiveLoops.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenReactiveLoops.ts deleted file mode 100644 index 2119b8c16729f..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenReactiveLoops.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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 { - ReactiveFunction, - ReactiveScopeBlock, - ReactiveStatement, - ReactiveTerminal, - ReactiveTerminalStatement, -} from '../HIR/HIR'; -import {assertExhaustive} from '../Utils/utils'; -import { - ReactiveFunctionTransform, - Transformed, - visitReactiveFunction, -} from './visitors'; - -/* - * Given a reactive function, flattens any scopes contained within a loop construct. - * We won't initially support memoization within loops though this is possible in the future. - */ -export function flattenReactiveLoops(fn: ReactiveFunction): void { - visitReactiveFunction(fn, new Transform(), false); -} - -class Transform extends ReactiveFunctionTransform { - override transformScope( - scope: ReactiveScopeBlock, - isWithinLoop: boolean, - ): Transformed { - this.visitScope(scope, isWithinLoop); - if (isWithinLoop) { - return { - kind: 'replace', - value: { - kind: 'pruned-scope', - scope: scope.scope, - instructions: scope.instructions, - }, - }; - } else { - return {kind: 'keep'}; - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - isWithinLoop: boolean, - ): void { - switch (stmt.terminal.kind) { - // Loop terminals flatten nested scopes - case 'do-while': - case 'while': - case 'for': - case 'for-of': - case 'for-in': { - this.traverseTerminal(stmt, true); - break; - } - // Non-loop terminals passthrough is contextual, inherits the parent isWithinScope - case 'try': - case 'label': - case 'break': - case 'continue': - case 'if': - case 'return': - case 'switch': - case 'throw': { - this.traverseTerminal(stmt, isWithinLoop); - break; - } - default: { - assertExhaustive( - stmt.terminal, - `Unexpected terminal kind \`${(stmt.terminal as any).kind}\``, - ); - } - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUse.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUse.ts deleted file mode 100644 index 753cd3d6e87b7..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUse.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * 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 { - Environment, - InstructionId, - ReactiveFunction, - ReactiveScopeBlock, - ReactiveStatement, - ReactiveValue, - getHookKind, - isUseOperator, -} from '../HIR'; -import { - ReactiveFunctionTransform, - Transformed, - visitReactiveFunction, -} from './visitors'; - -/** - * For simplicity the majority of compiler passes do not treat hooks specially. However, hooks are different - * from regular functions in two key ways: - * - They can introduce reactivity even when their arguments are non-reactive (accounted for in InferReactivePlaces) - * - They cannot be called conditionally - * - * The `use` operator is similar: - * - It can access context, and therefore introduce reactivity - * - It can be called conditionally, but _it must be called if the component needs the return value_. This is because - * React uses the fact that use was called to remember that the component needs the value, and that changes to the - * input should invalidate the component itself. - * - * This pass accounts for the "can't call conditionally" aspect of both hooks and use. Though the reasoning is slightly - * different for reach, the result is that we can't memoize scopes that call hooks or use since this would make them - * called conditionally in the output. - * - * The pass finds and removes any scopes that transitively contain a hook or use call. By running all - * the reactive scope inference first, agnostic of hooks, we know that the reactive scopes accurately - * describe the set of values which "construct together", and remove _all_ that memoization in order - * to ensure the hook call does not inadvertently become conditional. - */ -export function flattenScopesWithHooksOrUse(fn: ReactiveFunction): void { - visitReactiveFunction(fn, new Transform(), { - env: fn.env, - hasHook: false, - }); -} - -type State = { - env: Environment; - hasHook: boolean; -}; - -class Transform extends ReactiveFunctionTransform { - override transformScope( - scope: ReactiveScopeBlock, - outerState: State, - ): Transformed { - const innerState: State = { - env: outerState.env, - hasHook: false, - }; - this.visitScope(scope, innerState); - outerState.hasHook ||= innerState.hasHook; - if (innerState.hasHook) { - if (scope.instructions.length === 1) { - /* - * This was a scope just for a hook call, which doesn't need memoization. - * flatten it away - */ - return { - kind: 'replace-many', - value: scope.instructions, - }; - } - /* - * else this scope had multiple instructions and produced some other value: - * mark it as pruned - */ - return { - kind: 'replace', - value: { - kind: 'pruned-scope', - scope: scope.scope, - instructions: scope.instructions, - }, - }; - } else { - return {kind: 'keep'}; - } - } - - override visitValue( - id: InstructionId, - value: ReactiveValue, - state: State, - ): void { - this.traverseValue(id, value, state); - switch (value.kind) { - case 'CallExpression': { - if ( - getHookKind(state.env, value.callee.identifier) != null || - isUseOperator(value.callee.identifier) - ) { - state.hasHook = true; - } - break; - } - case 'MethodCall': { - if ( - getHookKind(state.env, value.property.identifier) != null || - isUseOperator(value.property.identifier) - ) { - state.hasHook = true; - } - break; - } - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeOverlappingReactiveScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeOverlappingReactiveScopes.ts deleted file mode 100644 index 733730fdec5f2..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeOverlappingReactiveScopes.ts +++ /dev/null @@ -1,281 +0,0 @@ -/** - * 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 { - InstructionId, - makeInstructionId, - Place, - ReactiveBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveScope, - ScopeId, -} from '../HIR'; -import DisjointSet from '../Utils/DisjointSet'; -import {retainWhere} from '../Utils/utils'; -import {getPlaceScope} from './BuildReactiveBlocks'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Note: this is the 3rd of 4 passes that determine how to break a function into discrete - * reactive scopes (independently memoizeable units of code): - * 1. InferReactiveScopeVariables (on HIR) determines operands that mutate together and assigns - * them a unique reactive scope. - * 2. AlignReactiveScopesToBlockScopes (on ReactiveFunction) aligns reactive scopes - * to block scopes. - * 3. MergeOverlappingReactiveScopes (this pass, on ReactiveFunction) ensures that reactive - * scopes do not overlap, merging any such scopes. - * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into - * a ReactiveScopeBlock. - * - * Previous passes may leave "overlapping" scopes, ie where one or more instructions are within - * the mutable range of multiple reactive scopes. We prefer to avoid executing instructions twice - * for performance reasons (side effects are less of a concern bc components are required to be - * idempotent), so we cannot simply repeat the instruction once for each scope. Instead, the only - * option is to combine the two scopes into one. This is an area where an eventual Forget IDE - * could provide real-time feedback to the developer that two computations are accidentally merged. - * - * ## Detailed Walkthrough - * - * Two scopes overlap if there is one or more instruction that is inside the range - * of both scopes. In general, overlapping scopes are merged togther. The only - * exception to this is when one scope *shadows* another scope. For example: - * - * ```javascript - * function foo(cond, a) { - * ⌵ scope for x - * let x = []; ⌝ - * if (cond) { ⎮ - * ⌵ scope for y ⎮ - * let y = []; ⌝ ⎮ - * if (b) { ⎮ ⎮ - * y.push(b); ⌟ ⎮ - * } ⎮ - * x.push(
{y}
); ⎮ - * } ⌟ - * } - * ``` - * - * In this example the two scopes overlap, but mutation of the two scopes is not - * interleaved. Specifically within the y scope there are no instructions that - * modify any other scope: the inner scope "shadows" the outer one. This category - * of overlap does *NOT* merge the scopes together. - * - * The implementation is inspired by the Rust notion of "stacked borrows". We traverse - * the control-flow graph in tree form, at each point keeping track of which scopes are - * active. So initially we see - * - * `let x = []` - * active scopes: [x] - * - * and mark the x scope as active. - * - * Then we later encounter - * - * `let y = [];` - * active scopes: [x, y] - * - * Here we first check to see if 'y' is already in the list of active scopes. It isn't, - * so we push it to the stop of the stack. - * - * Then - * - * `y.push(b)` - * active scopes: [x, y] - * - * Mutates y, so we check if y is the top of the stack. It is, so no merging must occur. - * - * If instead we saw eg - * - * `x.push(b)` - * active scopes: [x, y] - * - * Then we would see that 'x' is active, but that it is shadowed. The two scopes would have - * to be merged. - */ -export function mergeOverlappingReactiveScopes(fn: ReactiveFunction): void { - const context = new Context(); - visitReactiveFunction(fn, new Visitor(), context); - context.complete(); -} - -class Visitor extends ReactiveFunctionVisitor { - override visitID(id: InstructionId, state: Context): void { - state.visitId(id); - } - override visitPlace(id: InstructionId, place: Place, state: Context): void { - state.visitPlace(id, place); - } - override visitLValue(id: InstructionId, lvalue: Place, state: Context): void { - state.visitPlace(id, lvalue); - } - override visitBlock(block: ReactiveBlock, state: Context): void { - state.enter(() => { - this.traverseBlock(block, state); - }); - } - override visitInstruction( - instruction: ReactiveInstruction, - state: Context, - ): void { - if ( - instruction.value.kind === 'ConditionalExpression' || - instruction.value.kind === 'LogicalExpression' || - instruction.value.kind === 'OptionalExpression' - ) { - state.enter(() => { - super.visitInstruction(instruction, state); - }); - } else { - super.visitInstruction(instruction, state); - } - } -} - -class BlockScope { - seen: Set = new Set(); - scopes: Array = []; -} - -type ShadowableReactiveScope = { - scope: ReactiveScope; - shadowedBy: ReactiveScope | null; -}; - -class Context { - scopes: Array = []; - seenScopes: Set = new Set(); - joinedScopes: DisjointSet = new DisjointSet(); - operandScopes: Map = new Map(); - - visitId(id: InstructionId): void { - const currentBlock = this.scopes[this.scopes.length - 1]!; - retainWhere(currentBlock.scopes, pending => { - if (pending.scope.range.end > id) { - return true; - } else { - currentBlock.seen.delete(pending.scope.id); - return false; - } - }); - } - - visitPlace(id: InstructionId, place: Place): void { - const scope = getPlaceScope(id, place); - if (scope === null) { - return; - } - this.operandScopes.set(place, scope); - const currentBlock = this.scopes[this.scopes.length - 1]!; - // Fast-path for the first time we see a new scope - if (!this.seenScopes.has(scope.id)) { - this.seenScopes.add(scope.id); - currentBlock.seen.add(scope.id); - currentBlock.scopes.push({shadowedBy: null, scope}); - return; - } - // Scope has already been seen, find it in the current block or a parent - let index = this.scopes.length - 1; - let nextBlock = currentBlock; - while (!nextBlock.seen.has(scope.id)) { - /* - * scopes that cross control-flow boundaries are merged with overlapping - * scopes - */ - this.joinedScopes.union([scope, ...nextBlock.scopes.map(s => s.scope)]); - index--; - if (index < 0) { - /* - * TODO: handle reassignments in multiple branches. these create new identifiers that - * add an entry to this.seenScopes but which are then removed when their blocks exit. - * this is also wrong for codegen, different versions of an identifier could be cached - * differently and so a reassigned version of a variable needs a separate declaration. - * console.log(`scope ${scope.id} not found`); - */ - - /* - * for (let i = this.scopes.length - 1; i > index; i--) { - * const s = this.scopes[i]; - * console.log( - * JSON.stringify( - * { - * seen: Array.from(s.seen), - * scopes: s.scopes, - * }, - * null, - * 2 - * ) - * ); - * } - */ - currentBlock.seen.add(scope.id); - currentBlock.scopes.push({shadowedBy: null, scope}); - return; - } - nextBlock = this.scopes[index]!; - } - - // Handle interleaving within a given block scope - let found = false; - for (let i = 0; i < nextBlock.scopes.length; i++) { - const current = nextBlock.scopes[i]!; - if (current.scope.id === scope.id) { - found = true; - if (current.shadowedBy !== null) { - this.joinedScopes.union([current.shadowedBy, current.scope]); - } - } else if (found && current.shadowedBy === null) { - // `scope` is shadowing `current` and may interleave - current.shadowedBy = scope; - if (current.scope.range.end > scope.range.end) { - /* - * Current is shadowed by `scope`, and we know that `current` will mutate - * again (per its range), so the scopes are already known to interleave. - * - * Eagerly extend the ranges of the scopes so that we don't prematurely end - * a scope relative to its eventual post-merge mutable range - */ - const end = makeInstructionId( - Math.max(current.scope.range.end, scope.range.end), - ); - current.scope.range.end = end; - scope.range.end = end; - this.joinedScopes.union([current.scope, scope]); - } - } - } - if (!currentBlock.seen.has(scope.id)) { - currentBlock.seen.add(scope.id); - currentBlock.scopes.push({shadowedBy: null, scope}); - } - } - - enter(fn: () => void): void { - this.scopes.push(new BlockScope()); - fn(); - this.scopes.pop(); - } - - complete(): void { - this.joinedScopes.forEach((scope, groupScope) => { - if (scope !== groupScope) { - groupScope.range.start = makeInstructionId( - Math.min(groupScope.range.start, scope.range.start), - ); - groupScope.range.end = makeInstructionId( - Math.max(groupScope.range.end, scope.range.end), - ); - } - }); - for (const [operand, originalScope] of this.operandScopes) { - const mergedScope = this.joinedScopes.find(originalScope); - if (mergedScope !== null) { - operand.identifier.scope = mergedScope; - } - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts index 8033d05e2b3e3..5a9aa6b2a7368 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts @@ -26,7 +26,7 @@ import { } from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; import {assertExhaustive, getOrInsertDefault} from '../Utils/utils'; -import {getPlaceScope} from './BuildReactiveBlocks'; +import {getPlaceScope} from '../HIR/HIR'; import { ReactiveFunctionTransform, ReactiveFunctionVisitor, diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index 55f67fc2f7d23..eb778305611cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -6,18 +6,13 @@ */ export {alignObjectMethodScopes} from './AlignObjectMethodScopes'; -export {alignReactiveScopesToBlockScopes} from './AlignReactiveScopesToBlockScopes'; export {assertScopeInstructionsWithinScopes} from './AssertScopeInstructionsWithinScope'; export {assertWellFormedBreakTargets} from './AssertWellFormedBreakTargets'; -export {buildReactiveBlocks} from './BuildReactiveBlocks'; export {buildReactiveFunction} from './BuildReactiveFunction'; export {codegenFunction, type CodegenFunction} from './CodegenReactiveFunction'; export {extractScopeDeclarationsFromDestructuring} from './ExtractScopeDeclarationsFromDestructuring'; -export {flattenReactiveLoops} from './FlattenReactiveLoops'; -export {flattenScopesWithHooksOrUse} from './FlattenScopesWithHooksOrUse'; export {inferReactiveScopeVariables} from './InferReactiveScopeVariables'; export {memoizeFbtAndMacroOperandsInSameScope} from './MemoizeFbtAndMacroOperandsInSameScope'; -export {mergeOverlappingReactiveScopes} from './MergeOverlappingReactiveScopes'; export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesThatInvalidateTogether'; export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.expect.md index d59fb182c3cdb..97b3bb13d7017 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.expect.md @@ -2,8 +2,6 @@ ## Input ```javascript -// @enableReactiveScopesInHIR:false - import {Stringify, identity, makeArray, mutate} from 'shared-runtime'; /** @@ -37,8 +35,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false - +import { c as _c } from "react/compiler-runtime"; import { Stringify, identity, makeArray, mutate } from "shared-runtime"; /** @@ -52,11 +49,12 @@ import { Stringify, identity, makeArray, mutate } from "shared-runtime"; * handles this correctly. */ function Foo(t0) { - const $ = _c(4); + const $ = _c(3); const { cond1, cond2 } = t0; - const arr = makeArray({ a: 2 }, 2, []); let t1; - if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== arr) { + if ($[0] !== cond1 || $[1] !== cond2) { + const arr = makeArray({ a: 2 }, 2, []); + t1 = cond1 ? ( <>
{identity("foo")}
@@ -65,10 +63,9 @@ function Foo(t0) { ) : null; $[0] = cond1; $[1] = cond2; - $[2] = arr; - $[3] = t1; + $[2] = t1; } else { - t1 = $[3]; + t1 = $[2]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.tsx index a8f46eaf38a54..b5e2fa0c19ece 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.tsx @@ -1,5 +1,3 @@ -// @enableReactiveScopesInHIR:false - import {Stringify, identity, makeArray, mutate} from 'shared-runtime'; /** 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 deleted file mode 100644 index 9dee3bb8a705e..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/allocating-logical-expression-instruction-scope.expect.md +++ /dev/null @@ -1,61 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false - -/** - * This is a weird case as data has type `BuiltInMixedReadonly`. - * The only scoped value we currently infer in this program is the - * PropertyLoad `data?.toString`. - */ -import {useFragment} from 'shared-runtime'; - -function Foo() { - const data = useFragment(); - return [data?.toString() || '']; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false - -/** - * This is a weird case as data has type `BuiltInMixedReadonly`. - * The only scoped value we currently infer in this program is the - * PropertyLoad `data?.toString`. - */ -import { useFragment } from "shared-runtime"; - -function Foo() { - const $ = _c(2); - const data = useFragment(); - const t0 = data?.toString() || ""; - let t1; - if ($[0] !== t0) { - t1 = [t0]; - $[0] = t0; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [], -}; - -``` - -### Eval output -(kind: ok) ["[object Object]"] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/allocating-logical-expression-instruction-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/allocating-logical-expression-instruction-scope.ts deleted file mode 100644 index f77a0fb8285e4..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/allocating-logical-expression-instruction-scope.ts +++ /dev/null @@ -1,18 +0,0 @@ -// @enableReactiveScopesInHIR:false - -/** - * This is a weird case as data has type `BuiltInMixedReadonly`. - * The only scoped value we currently infer in this program is the - * PropertyLoad `data?.toString`. - */ -import {useFragment} from 'shared-runtime'; - -function Foo() { - const data = useFragment(); - return [data?.toString() || '']; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-hoisted-declaration-with-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-hoisted-declaration-with-scope.expect.md deleted file mode 100644 index 8b1b8dabba013..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-hoisted-declaration-with-scope.expect.md +++ /dev/null @@ -1,96 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false -import {StaticText1, Stringify, identity, useHook} from 'shared-runtime'; -/** - * `button` and `dispatcher` must end up in the same memo block. It would be - * invalid for `button` to take a dependency on `dispatcher` as dispatcher - * is created later. - * - * Sprout error: - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) "[[ function params=1 ]]" - * Forget: - * (kind: exception) Cannot access 'dispatcher' before initialization - */ -function useFoo({onClose}) { - const button = StaticText1 ?? ( - { - dispatcher.go('route2'); - }, - }} - /> - ); - - const dispatcher = useHook(); - - return button; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{onClose: identity()}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false -import { StaticText1, Stringify, identity, useHook } from "shared-runtime"; -/** - * `button` and `dispatcher` must end up in the same memo block. It would be - * invalid for `button` to take a dependency on `dispatcher` as dispatcher - * is created later. - * - * Sprout error: - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) "[[ function params=1 ]]" - * Forget: - * (kind: exception) Cannot access 'dispatcher' before initialization - */ -function useFoo(t0) { - const $ = _c(3); - const { onClose } = t0; - let t1; - if ($[0] !== onClose || $[1] !== dispatcher) { - t1 = StaticText1 ?? ( - { - dispatcher.go("route2"); - }, - }} - /> - ); - $[0] = onClose; - $[1] = dispatcher; - $[2] = t1; - } else { - t1 = $[2]; - } - const button = t1; - - const dispatcher = useHook(); - return button; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ onClose: identity() }], -}; - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-hoisted-declaration-with-scope.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-hoisted-declaration-with-scope.tsx deleted file mode 100644 index e3883e6b4c035..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-hoisted-declaration-with-scope.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// @enableReactiveScopesInHIR:false -import {StaticText1, Stringify, identity, useHook} from 'shared-runtime'; -/** - * `button` and `dispatcher` must end up in the same memo block. It would be - * invalid for `button` to take a dependency on `dispatcher` as dispatcher - * is created later. - * - * Sprout error: - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) "[[ function params=1 ]]" - * Forget: - * (kind: exception) Cannot access 'dispatcher' before initialization - */ -function useFoo({onClose}) { - const button = StaticText1 ?? ( - { - dispatcher.go('route2'); - }, - }} - /> - ); - - const dispatcher = useHook(); - - return button; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{onClose: identity()}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block.expect.md deleted file mode 100644 index 1fce0aec9d2e8..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block.expect.md +++ /dev/null @@ -1,137 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false -import {identity, mutate} from 'shared-runtime'; - -/** - * The root cause of this bug is in `InferReactiveScopeVariables`. Currently, - * InferReactiveScopeVariables do not ensure that maybe-aliased values get - * assigned the same reactive scope. This is safe only when an already- - * constructed value is captured, e.g. - * ```js - * const x = makeObj(); ⌝ mutable range of x - * mutate(x); ⌟ - * <-- after this point, we can produce a canonical version - * of x for all following aliases - * const y = []; - * y.push(x); <-- y captures x - * ``` - * - * However, if a value is captured/aliased during its mutable range and the - * capturing container is separately memoized, it becomes difficult to guarantee - * that all aliases refer to the same value. - * - * Sprout error: - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) [{"wat0":"joe"},3] - * [{"wat0":"joe"},3] - * Forget: - * (kind: ok) [{"wat0":"joe"},3] - * [[ (exception in render) Error: oh no! ]] - * - */ -function useFoo({a, b}) { - const x = {a}; - const y = {}; - mutate(x); - const z = [identity(y), b]; - mutate(y); - - if (z[0] !== y) { - throw new Error('oh no!'); - } - return z; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{a: 2, b: 3}], - sequentialRenders: [ - {a: 2, b: 3}, - {a: 4, b: 3}, - ], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false -import { identity, mutate } from "shared-runtime"; - -/** - * The root cause of this bug is in `InferReactiveScopeVariables`. Currently, - * InferReactiveScopeVariables do not ensure that maybe-aliased values get - * assigned the same reactive scope. This is safe only when an already- - * constructed value is captured, e.g. - * ```js - * const x = makeObj(); ⌝ mutable range of x - * mutate(x); ⌟ - * <-- after this point, we can produce a canonical version - * of x for all following aliases - * const y = []; - * y.push(x); <-- y captures x - * ``` - * - * However, if a value is captured/aliased during its mutable range and the - * capturing container is separately memoized, it becomes difficult to guarantee - * that all aliases refer to the same value. - * - * Sprout error: - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) [{"wat0":"joe"},3] - * [{"wat0":"joe"},3] - * Forget: - * (kind: ok) [{"wat0":"joe"},3] - * [[ (exception in render) Error: oh no! ]] - * - */ -function useFoo(t0) { - const $ = _c(6); - const { a, b } = t0; - let z; - let y; - if ($[0] !== a || $[1] !== b) { - const x = { a }; - y = {}; - mutate(x); - let t1; - if ($[4] !== b) { - t1 = [identity(y), b]; - $[4] = b; - $[5] = t1; - } else { - t1 = $[5]; - } - z = t1; - mutate(y); - $[0] = a; - $[1] = b; - $[2] = z; - $[3] = y; - } else { - z = $[2]; - y = $[3]; - } - if (z[0] !== y) { - throw new Error("oh no!"); - } - return z; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ a: 2, b: 3 }], - sequentialRenders: [ - { a: 2, b: 3 }, - { a: 4, b: 3 }, - ], -}; - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block.ts deleted file mode 100644 index f3c5145b3f323..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block.ts +++ /dev/null @@ -1,52 +0,0 @@ -// @enableReactiveScopesInHIR:false -import {identity, mutate} from 'shared-runtime'; - -/** - * The root cause of this bug is in `InferReactiveScopeVariables`. Currently, - * InferReactiveScopeVariables do not ensure that maybe-aliased values get - * assigned the same reactive scope. This is safe only when an already- - * constructed value is captured, e.g. - * ```js - * const x = makeObj(); ⌝ mutable range of x - * mutate(x); ⌟ - * <-- after this point, we can produce a canonical version - * of x for all following aliases - * const y = []; - * y.push(x); <-- y captures x - * ``` - * - * However, if a value is captured/aliased during its mutable range and the - * capturing container is separately memoized, it becomes difficult to guarantee - * that all aliases refer to the same value. - * - * Sprout error: - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) [{"wat0":"joe"},3] - * [{"wat0":"joe"},3] - * Forget: - * (kind: ok) [{"wat0":"joe"},3] - * [[ (exception in render) Error: oh no! ]] - * - */ -function useFoo({a, b}) { - const x = {a}; - const y = {}; - mutate(x); - const z = [identity(y), b]; - mutate(y); - - if (z[0] !== y) { - throw new Error('oh no!'); - } - return z; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{a: 2, b: 3}], - sequentialRenders: [ - {a: 2, b: 3}, - {a: 4, b: 3}, - ], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/error.capture-ref-for-later-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/error.capture-ref-for-later-mutation.expect.md deleted file mode 100644 index f0b0e6f3a8679..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/error.capture-ref-for-later-mutation.expect.md +++ /dev/null @@ -1,51 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false -import {useRef} from 'react'; -import {addOne} from 'shared-runtime'; - -function useKeyCommand() { - const currentPosition = useRef(0); - const handleKey = direction => () => { - const position = currentPosition.current; - const nextPosition = direction === 'left' ? addOne(position) : position; - currentPosition.current = nextPosition; - }; - const moveLeft = { - handler: handleKey('left'), - }; - const moveRight = { - handler: handleKey('right'), - }; - return [moveLeft, moveRight]; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useKeyCommand, - params: [], -}; - -``` - - -## Error - -``` - 11 | }; - 12 | const moveLeft = { -> 13 | handler: handleKey('left'), - | ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (13:13) - -InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (13:13) - -InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (16:16) - -InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (16:16) - 14 | }; - 15 | const moveRight = { - 16 | handler: handleKey('right'), -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/error.capture-ref-for-later-mutation.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/error.capture-ref-for-later-mutation.tsx deleted file mode 100644 index 6f27dfe07fb33..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/error.capture-ref-for-later-mutation.tsx +++ /dev/null @@ -1,24 +0,0 @@ -// @enableReactiveScopesInHIR:false -import {useRef} from 'react'; -import {addOne} from 'shared-runtime'; - -function useKeyCommand() { - const currentPosition = useRef(0); - const handleKey = direction => () => { - const position = currentPosition.current; - const nextPosition = direction === 'left' ? addOne(position) : position; - currentPosition.current = nextPosition; - }; - const moveLeft = { - handler: handleKey('left'), - }; - const moveRight = { - handler: handleKey('right'), - }; - return [moveLeft, moveRight]; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useKeyCommand, - params: [], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/invalid-align-scopes-within-nested-valueblock-in-array.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/invalid-align-scopes-within-nested-valueblock-in-array.expect.md deleted file mode 100644 index d59fb182c3cdb..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/invalid-align-scopes-within-nested-valueblock-in-array.expect.md +++ /dev/null @@ -1,84 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false - -import {Stringify, identity, makeArray, mutate} from 'shared-runtime'; - -/** - * Here, identity('foo') is an immutable allocating instruction. - * `arr` is a mutable value whose mutable range ends at `arr.map`. - * - * The previous (reactive function) version of alignScopesToBlocks set the range of - * both scopes to end at value blocks within the <> expression. - * However, both scope ranges should be aligned to the outer value block - * (e.g. `cond1 ? <>: null`). The HIR version of alignScopesToBlocks - * handles this correctly. - */ -function Foo({cond1, cond2}) { - const arr = makeArray({a: 2}, 2, []); - - return cond1 ? ( - <> -
{identity('foo')}
- - - ) : null; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond1: true, cond2: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false - -import { Stringify, identity, makeArray, mutate } from "shared-runtime"; - -/** - * Here, identity('foo') is an immutable allocating instruction. - * `arr` is a mutable value whose mutable range ends at `arr.map`. - * - * The previous (reactive function) version of alignScopesToBlocks set the range of - * both scopes to end at value blocks within the <> expression. - * However, both scope ranges should be aligned to the outer value block - * (e.g. `cond1 ? <>: null`). The HIR version of alignScopesToBlocks - * handles this correctly. - */ -function Foo(t0) { - const $ = _c(4); - const { cond1, cond2 } = t0; - const arr = makeArray({ a: 2 }, 2, []); - let t1; - if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== arr) { - t1 = cond1 ? ( - <> -
{identity("foo")}
- - - ) : null; - $[0] = cond1; - $[1] = cond2; - $[2] = arr; - $[3] = t1; - } else { - t1 = $[3]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond1: true, cond2: true }], -}; - -``` - -### Eval output -(kind: ok)
foo
{"value":[null,null,null]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/invalid-align-scopes-within-nested-valueblock-in-array.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/invalid-align-scopes-within-nested-valueblock-in-array.tsx deleted file mode 100644 index a8f46eaf38a54..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/invalid-align-scopes-within-nested-valueblock-in-array.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// @enableReactiveScopesInHIR:false - -import {Stringify, identity, makeArray, mutate} from 'shared-runtime'; - -/** - * Here, identity('foo') is an immutable allocating instruction. - * `arr` is a mutable value whose mutable range ends at `arr.map`. - * - * The previous (reactive function) version of alignScopesToBlocks set the range of - * both scopes to end at value blocks within the <> expression. - * However, both scope ranges should be aligned to the outer value block - * (e.g. `cond1 ? <>: null`). The HIR version of alignScopesToBlocks - * handles this correctly. - */ -function Foo({cond1, cond2}) { - const arr = makeArray({a: 2}, 2, []); - - return cond1 ? ( - <> -
{identity('foo')}
- - - ) : null; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond1: true, cond2: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutate-outer-scope-within-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutate-outer-scope-within-value-block.expect.md deleted file mode 100644 index 62da2a8e76591..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutate-outer-scope-within-value-block.expect.md +++ /dev/null @@ -1,101 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false -import {CONST_TRUE, identity, shallowCopy} from 'shared-runtime'; - -/** - * There are three values with their own scopes in this fixture. - * - arr, whose mutable range extends to the `mutate(...)` call - * - cond, which has a mutable range of exactly 1 (e.g. created but not - * mutated) - * - { val: CONST_TRUE }, which is also not mutated after creation. However, - * its scope range becomes extended to the value block. - * - * After AlignScopesToBlockScopes, our scopes look roughly like this - * ```js - * [1] arr = shallowCopy() ⌝@0 - * [2] cond = identity() <- @1 | - * [3] $0 = Ternary test=cond ⌝@2 | - * [4] {val : CONST_TRUE} | | - * [5] mutate(arr) | | - * [6] return $0 ⌟ ⌟ - * ``` - * - * Observe that instruction 5 mutates scope 0, which means that scopes 0 and 2 - * should be merged. - */ -function useFoo({input}) { - const arr = shallowCopy(input); - - const cond = identity(false); - return cond ? {val: CONST_TRUE} : mutate(arr); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{input: 3}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false -import { CONST_TRUE, identity, shallowCopy } from "shared-runtime"; - -/** - * There are three values with their own scopes in this fixture. - * - arr, whose mutable range extends to the `mutate(...)` call - * - cond, which has a mutable range of exactly 1 (e.g. created but not - * mutated) - * - { val: CONST_TRUE }, which is also not mutated after creation. However, - * its scope range becomes extended to the value block. - * - * After AlignScopesToBlockScopes, our scopes look roughly like this - * ```js - * [1] arr = shallowCopy() ⌝@0 - * [2] cond = identity() <- @1 | - * [3] $0 = Ternary test=cond ⌝@2 | - * [4] {val : CONST_TRUE} | | - * [5] mutate(arr) | | - * [6] return $0 ⌟ ⌟ - * ``` - * - * Observe that instruction 5 mutates scope 0, which means that scopes 0 and 2 - * should be merged. - */ -function useFoo(t0) { - const $ = _c(3); - const { input } = t0; - const arr = shallowCopy(input); - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = identity(false); - $[0] = t1; - } else { - t1 = $[0]; - } - const cond = t1; - let t2; - if ($[1] !== arr) { - t2 = cond ? { val: CONST_TRUE } : mutate(arr); - $[1] = arr; - $[2] = t2; - } else { - t2 = $[2]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ input: 3 }], -}; - -``` - -### Eval output -(kind: exception) mutate is not defined \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutate-outer-scope-within-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutate-outer-scope-within-value-block.ts deleted file mode 100644 index 9b19b3fd7ee9e..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutate-outer-scope-within-value-block.ts +++ /dev/null @@ -1,35 +0,0 @@ -// @enableReactiveScopesInHIR:false -import {CONST_TRUE, identity, shallowCopy} from 'shared-runtime'; - -/** - * There are three values with their own scopes in this fixture. - * - arr, whose mutable range extends to the `mutate(...)` call - * - cond, which has a mutable range of exactly 1 (e.g. created but not - * mutated) - * - { val: CONST_TRUE }, which is also not mutated after creation. However, - * its scope range becomes extended to the value block. - * - * After AlignScopesToBlockScopes, our scopes look roughly like this - * ```js - * [1] arr = shallowCopy() ⌝@0 - * [2] cond = identity() <- @1 | - * [3] $0 = Ternary test=cond ⌝@2 | - * [4] {val : CONST_TRUE} | | - * [5] mutate(arr) | | - * [6] return $0 ⌟ ⌟ - * ``` - * - * Observe that instruction 5 mutates scope 0, which means that scopes 0 and 2 - * should be merged. - */ -function useFoo({input}) { - const arr = shallowCopy(input); - - const cond = identity(false); - return cond ? {val: CONST_TRUE} : mutate(arr); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{input: 3}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-capture-and-mutablerange.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-capture-and-mutablerange.expect.md deleted file mode 100644 index 8b8a5f5cc4918..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-capture-and-mutablerange.expect.md +++ /dev/null @@ -1,92 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false -import {mutate} from 'shared-runtime'; - -/** - * This test fixture is similar to mutation-within-jsx. The only difference - * is that there is no `freeze` effect here, which means that `z` may be - * mutated after its memo block through mutating `y`. - * - * While this is technically correct (as `z` is a nested memo block), it - * is an edge case as we believe that values are not mutated after their - * memo blocks (which may lead to 'tearing', i.e. mutating one render's - * values in a subsequent render. - */ -function useFoo({a, b}) { - // x and y's scopes start here - const x = {a}; - const y = [b]; - mutate(x); - // z captures the result of `mutate(y)`, which may be aliased to `y`. - const z = [mutate(y)]; - // the following line may also mutate z - mutate(y); - // and end here - return z; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{a: 2, b: 3}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false -import { mutate } from "shared-runtime"; - -/** - * This test fixture is similar to mutation-within-jsx. The only difference - * is that there is no `freeze` effect here, which means that `z` may be - * mutated after its memo block through mutating `y`. - * - * While this is technically correct (as `z` is a nested memo block), it - * is an edge case as we believe that values are not mutated after their - * memo blocks (which may lead to 'tearing', i.e. mutating one render's - * values in a subsequent render. - */ -function useFoo(t0) { - const $ = _c(5); - const { a, b } = t0; - let z; - if ($[0] !== a || $[1] !== b) { - const x = { a }; - const y = [b]; - mutate(x); - - const t1 = mutate(y); - let t2; - if ($[3] !== t1) { - t2 = [t1]; - $[3] = t1; - $[4] = t2; - } else { - t2 = $[4]; - } - z = t2; - - mutate(y); - $[0] = a; - $[1] = b; - $[2] = z; - } else { - z = $[2]; - } - return z; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ a: 2, b: 3 }], -}; - -``` - -### Eval output -(kind: ok) [null] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-capture-and-mutablerange.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-capture-and-mutablerange.tsx deleted file mode 100644 index 64d6c0453398c..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-capture-and-mutablerange.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// @enableReactiveScopesInHIR:false -import {mutate} from 'shared-runtime'; - -/** - * This test fixture is similar to mutation-within-jsx. The only difference - * is that there is no `freeze` effect here, which means that `z` may be - * mutated after its memo block through mutating `y`. - * - * While this is technically correct (as `z` is a nested memo block), it - * is an edge case as we believe that values are not mutated after their - * memo blocks (which may lead to 'tearing', i.e. mutating one render's - * values in a subsequent render. - */ -function useFoo({a, b}) { - // x and y's scopes start here - const x = {a}; - const y = [b]; - mutate(x); - // z captures the result of `mutate(y)`, which may be aliased to `y`. - const z = [mutate(y)]; - // the following line may also mutate z - mutate(y); - // and end here - return z; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{a: 2, b: 3}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx-and-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx-and-break.expect.md deleted file mode 100644 index 254791cc49dd1..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx-and-break.expect.md +++ /dev/null @@ -1,99 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false -import { - Stringify, - makeObject_Primitives, - mutate, - mutateAndReturn, -} from 'shared-runtime'; - -function useFoo({data}) { - let obj = null; - let myDiv = null; - label: { - if (data.cond) { - obj = makeObject_Primitives(); - if (data.cond1) { - myDiv = ; - break label; - } - mutate(obj); - } - } - - return myDiv; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{data: {cond: true, cond1: true}}], - sequentialRenders: [ - {data: {cond: true, cond1: true}}, - {data: {cond: true, cond1: true}}, - ], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false -import { - Stringify, - makeObject_Primitives, - mutate, - mutateAndReturn, -} from "shared-runtime"; - -function useFoo(t0) { - const $ = _c(5); - const { data } = t0; - let obj; - let myDiv = null; - bb0: if (data.cond) { - if ($[0] !== data.cond1) { - obj = makeObject_Primitives(); - if (data.cond1) { - const t1 = mutateAndReturn(obj); - let t2; - if ($[3] !== t1) { - t2 = ; - $[3] = t1; - $[4] = t2; - } else { - t2 = $[4]; - } - myDiv = t2; - break bb0; - } - - mutate(obj); - $[0] = data.cond1; - $[1] = obj; - $[2] = myDiv; - } else { - obj = $[1]; - myDiv = $[2]; - } - } - return myDiv; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ data: { cond: true, cond1: true } }], - sequentialRenders: [ - { data: { cond: true, cond1: true } }, - { data: { cond: true, cond1: true } }, - ], -}; - -``` - -### Eval output -(kind: ok)
{"value":{"a":0,"b":"value1","c":true,"wat0":"joe"}}
-
{"value":{"a":0,"b":"value1","c":true,"wat0":"joe"}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx-and-break.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx-and-break.tsx deleted file mode 100644 index 5d33cc211c7bc..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx-and-break.tsx +++ /dev/null @@ -1,33 +0,0 @@ -// @enableReactiveScopesInHIR:false -import { - Stringify, - makeObject_Primitives, - mutate, - mutateAndReturn, -} from 'shared-runtime'; - -function useFoo({data}) { - let obj = null; - let myDiv = null; - label: { - if (data.cond) { - obj = makeObject_Primitives(); - if (data.cond1) { - myDiv = ; - break label; - } - mutate(obj); - } - } - - return myDiv; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{data: {cond: true, cond1: true}}], - sequentialRenders: [ - {data: {cond: true, cond1: true}}, - {data: {cond: true, cond1: true}}, - ], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx.expect.md deleted file mode 100644 index 13a69bc2a6ab3..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx.expect.md +++ /dev/null @@ -1,141 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false -import { - Stringify, - makeObject_Primitives, - mutateAndReturn, -} from 'shared-runtime'; - -/** - * In this example, the `` JSX block mutates then captures obj. - * As JSX expressions freeze their values, we know that `obj` and `myDiv` cannot - * be mutated past this. - * This set of mutable range + scopes is an edge case because the JSX expression - * references values in two scopes. - * - (freeze) the result of `mutateAndReturn` - * this is a mutable value with a mutable range starting at `makeObject()` - * - (mutate) the lvalue storing the result of `` - * this is a immutable value and so gets assigned a different scope - * - * obj@0 = makeObj(); ⌝ scope@0 - * if (cond) { | - * $1@0 = mutate(obj@0); | - * myDiv@1 = JSX $1@0 <- scope@1 | - * } ⌟ - * - * Coincidentally, the range of `obj` is extended by alignScopesToBlocks to *past* - * the end of the JSX instruction. As we currently alias identifier mutableRanges to - * scope ranges, this `freeze` reference is perceived as occurring during the mutable - * range of `obj` (even though it is after the last mutating reference). - * - * This case is technically safe as `myDiv` correctly takes `obj` as a dependency. As - * a result, developers can never observe myDiv can aliasing a different value generation - * than `obj` (e.g. the invariant `myDiv.props.value === obj` always holds). - */ -function useFoo({data}) { - let obj = null; - let myDiv = null; - if (data.cond) { - obj = makeObject_Primitives(); - if (data.cond1) { - myDiv = ; - } - } - return myDiv; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{data: {cond: true, cond1: true}}], - sequentialRenders: [ - {data: {cond: true, cond1: true}}, - {data: {cond: true, cond1: true}}, - ], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false -import { - Stringify, - makeObject_Primitives, - mutateAndReturn, -} from "shared-runtime"; - -/** - * In this example, the `` JSX block mutates then captures obj. - * As JSX expressions freeze their values, we know that `obj` and `myDiv` cannot - * be mutated past this. - * This set of mutable range + scopes is an edge case because the JSX expression - * references values in two scopes. - * - (freeze) the result of `mutateAndReturn` - * this is a mutable value with a mutable range starting at `makeObject()` - * - (mutate) the lvalue storing the result of `` - * this is a immutable value and so gets assigned a different scope - * - * obj@0 = makeObj(); ⌝ scope@0 - * if (cond) { | - * $1@0 = mutate(obj@0); | - * myDiv@1 = JSX $1@0 <- scope@1 | - * } ⌟ - * - * Coincidentally, the range of `obj` is extended by alignScopesToBlocks to *past* - * the end of the JSX instruction. As we currently alias identifier mutableRanges to - * scope ranges, this `freeze` reference is perceived as occurring during the mutable - * range of `obj` (even though it is after the last mutating reference). - * - * This case is technically safe as `myDiv` correctly takes `obj` as a dependency. As - * a result, developers can never observe myDiv can aliasing a different value generation - * than `obj` (e.g. the invariant `myDiv.props.value === obj` always holds). - */ -function useFoo(t0) { - const $ = _c(5); - const { data } = t0; - let obj; - let myDiv = null; - if (data.cond) { - if ($[0] !== data.cond1) { - obj = makeObject_Primitives(); - if (data.cond1) { - const t1 = mutateAndReturn(obj); - let t2; - if ($[3] !== t1) { - t2 = ; - $[3] = t1; - $[4] = t2; - } else { - t2 = $[4]; - } - myDiv = t2; - } - $[0] = data.cond1; - $[1] = obj; - $[2] = myDiv; - } else { - obj = $[1]; - myDiv = $[2]; - } - } - return myDiv; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ data: { cond: true, cond1: true } }], - sequentialRenders: [ - { data: { cond: true, cond1: true } }, - { data: { cond: true, cond1: true } }, - ], -}; - -``` - -### Eval output -(kind: ok)
{"value":{"a":0,"b":"value1","c":true,"wat0":"joe"}}
-
{"value":{"a":0,"b":"value1","c":true,"wat0":"joe"}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx.tsx deleted file mode 100644 index 06592044ee7b8..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/mutation-within-jsx.tsx +++ /dev/null @@ -1,53 +0,0 @@ -// @enableReactiveScopesInHIR:false -import { - Stringify, - makeObject_Primitives, - mutateAndReturn, -} from 'shared-runtime'; - -/** - * In this example, the `` JSX block mutates then captures obj. - * As JSX expressions freeze their values, we know that `obj` and `myDiv` cannot - * be mutated past this. - * This set of mutable range + scopes is an edge case because the JSX expression - * references values in two scopes. - * - (freeze) the result of `mutateAndReturn` - * this is a mutable value with a mutable range starting at `makeObject()` - * - (mutate) the lvalue storing the result of `` - * this is a immutable value and so gets assigned a different scope - * - * obj@0 = makeObj(); ⌝ scope@0 - * if (cond) { | - * $1@0 = mutate(obj@0); | - * myDiv@1 = JSX $1@0 <- scope@1 | - * } ⌟ - * - * Coincidentally, the range of `obj` is extended by alignScopesToBlocks to *past* - * the end of the JSX instruction. As we currently alias identifier mutableRanges to - * scope ranges, this `freeze` reference is perceived as occurring during the mutable - * range of `obj` (even though it is after the last mutating reference). - * - * This case is technically safe as `myDiv` correctly takes `obj` as a dependency. As - * a result, developers can never observe myDiv can aliasing a different value generation - * than `obj` (e.g. the invariant `myDiv.props.value === obj` always holds). - */ -function useFoo({data}) { - let obj = null; - let myDiv = null; - if (data.cond) { - obj = makeObject_Primitives(); - if (data.cond1) { - myDiv = ; - } - } - return myDiv; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{data: {cond: true, cond1: true}}], - sequentialRenders: [ - {data: {cond: true, cond1: true}}, - {data: {cond: true, cond1: true}}, - ], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/repro-allocating-ternary-test-instruction-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/repro-allocating-ternary-test-instruction-scope.expect.md deleted file mode 100644 index aebddad8a4e1c..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/repro-allocating-ternary-test-instruction-scope.expect.md +++ /dev/null @@ -1,61 +0,0 @@ - -## Input - -```javascript -// @enableReactiveScopesInHIR:false -import {identity, makeObject_Primitives} from 'shared-runtime'; - -function useTest({cond}) { - const val = makeObject_Primitives(); - - useHook(); - /** - * We don't technically need a reactive scope for this ternary as - * it cannot produce newly allocated values. - * While identity(...) may allocate, we can teach the compiler that - * its result is only used as as a test condition - */ - const result = identity(cond) ? val : null; - return result; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useTest, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false -import { identity, makeObject_Primitives } from "shared-runtime"; - -function useTest(t0) { - const $ = _c(1); - const { cond } = t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = makeObject_Primitives(); - $[0] = t1; - } else { - t1 = $[0]; - } - const val = t1; - - useHook(); - - const result = identity(cond) ? val : null; - return result; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useTest, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: exception) useHook is not defined \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/repro-allocating-ternary-test-instruction-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/repro-allocating-ternary-test-instruction-scope.ts deleted file mode 100644 index d52fcaa9f1042..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/original-reactive-scopes-fork/repro-allocating-ternary-test-instruction-scope.ts +++ /dev/null @@ -1,21 +0,0 @@ -// @enableReactiveScopesInHIR:false -import {identity, makeObject_Primitives} from 'shared-runtime'; - -function useTest({cond}) { - const val = makeObject_Primitives(); - - useHook(); - /** - * We don't technically need a reactive scope for this ternary as - * it cannot produce newly allocated values. - * While identity(...) may allocate, we can teach the compiler that - * its result is only used as as a test condition - */ - const result = identity(cond) ? val : null; - return result; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useTest, - params: [{cond: true}], -};