@samitouri / QOS-React-2 / commits / fd018af617

[compiler] Delete propagateScopeDeps (non-hir) (#31199)

`enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31199). * #31202 * #31203 * #31201 * #31200 * #31346 * __->__ #31199

mofeiZ committed Nov 5, 2024 at 19:22 UTC fd018af617cf9f8be607f45fc53d6d8167d29eb4
61 files changed +566 -1868
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+6 -18
@@ -57,7 +57,6 @@ import {
57 mergeReactiveScopesThatInvalidateTogether,
58 promoteUsedTemporaries,
59 propagateEarlyReturns,
60 - propagateScopeDependencies,
60 pruneHoistedContexts,
61 pruneNonEscapingScopes,
62 pruneNonReactiveDependencies,
@@ -348,14 +347,12 @@ function* runWithEnvironment(
347 });
348 assertTerminalSuccessorsExist(hir);
349 assertTerminalPredsExist(hir);
351 - if (env.config.enablePropagateDepsInHIR) {
352 - propagateScopeDependenciesHIR(hir);
353 - yield log({
354 - kind: 'hir',
355 - name: 'PropagateScopeDependenciesHIR',
356 - value: hir,
357 - });
358 - }
350 + propagateScopeDependenciesHIR(hir);
351 + yield log({
352 + kind: 'hir',
353 + name: 'PropagateScopeDependenciesHIR',
354 + value: hir,
355 + });
356
357 if (env.config.inlineJsxTransform) {
358 inlineJsxTransform(hir, env.config.inlineJsxTransform);
@@ -383,15 +380,6 @@ function* runWithEnvironment(
380 });
381 assertScopeInstructionsWithinScopes(reactiveFunction);
382
386 - if (!env.config.enablePropagateDepsInHIR) {
387 - propagateScopeDependencies(reactiveFunction);
388 - yield log({
389 - kind: 'reactive',
390 - name: 'PropagateScopeDependencies',
391 - value: reactiveFunction,
392 - });
393 - }
394 -
383 pruneNonEscapingScopes(reactiveFunction);
384 yield log({
385 kind: 'reactive',
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
-10
@@ -231,16 +231,6 @@ const EnvironmentConfigSchema = z.object({
231 */
232 enableUseTypeAnnotations: z.boolean().default(false),
233
234 - enablePropagateDepsInHIR: z.boolean().default(false),
235 -
236 - /**
237 - * Enables inference of optional dependency chains. Without this flag
238 - * a property chain such as `props?.items?.foo` will infer as a dep on
239 - * just `props`. With this flag enabled, we'll infer that full path as
240 - * the dependency.
241 - */
242 - enableOptionalDependencies: z.boolean().default(true),
243 -
234 /**
235 * Enables inlining ReactElement object literals in place of JSX
236 * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted
-1324
@@ -1,1324 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -
8 -import {CompilerError} from '../CompilerError';
9 -import {Environment} from '../HIR';
10 -import {
11 - areEqualPaths,
12 - BlockId,
13 - DeclarationId,
14 - GeneratedSource,
15 - Identifier,
16 - InstructionId,
17 - InstructionKind,
18 - isObjectMethodType,
19 - isRefValueType,
20 - isUseRefType,
21 - makeInstructionId,
22 - Place,
23 - PrunedReactiveScopeBlock,
24 - ReactiveFunction,
25 - ReactiveInstruction,
26 - ReactiveOptionalCallValue,
27 - ReactiveScope,
28 - ReactiveScopeBlock,
29 - ReactiveScopeDependency,
30 - ReactiveTerminalStatement,
31 - ReactiveValue,
32 - ScopeId,
33 -} from '../HIR/HIR';
34 -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors';
35 -import {empty, Stack} from '../Utils/Stack';
36 -import {assertExhaustive, Iterable_some} from '../Utils/utils';
37 -import {
38 - ReactiveScopeDependencyTree,
39 - ReactiveScopePropertyDependency,
40 -} from './DeriveMinimalDependencies';
41 -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
42 -
43 -/*
44 - * Infers the dependencies of each scope to include variables whose values
45 - * are non-stable and created prior to the start of the scope. Also propagates
46 - * dependencies upwards, so that parent scope dependencies are the union of
47 - * their direct dependencies and those of their child scopes.
48 - */
49 -export function propagateScopeDependencies(fn: ReactiveFunction): void {
50 - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = {
51 - declarations: new Map(),
52 - usedOutsideDeclaringScope: new Set(),
53 - };
54 - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries);
55 -
56 - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope);
57 - for (const param of fn.params) {
58 - if (param.kind === 'Identifier') {
59 - context.declare(param.identifier, {
60 - id: makeInstructionId(0),
61 - scope: empty(),
62 - });
63 - } else {
64 - context.declare(param.place.identifier, {
65 - id: makeInstructionId(0),
66 - scope: empty(),
67 - });
68 - }
69 - }
70 - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context);
71 -}
72 -
73 -type TemporariesUsedOutsideDefiningScope = {
74 - /*
75 - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad)
76 - * and the scope where they are defined
77 - */
78 - declarations: Map<DeclarationId, ScopeId>;
79 - // temporaries used outside of their defining scope
80 - usedOutsideDeclaringScope: Set<DeclarationId>;
81 -};
82 -class FindPromotedTemporaries extends ReactiveFunctionVisitor<TemporariesUsedOutsideDefiningScope> {
83 - scopes: Array<ScopeId> = [];
84 -
85 - override visitScope(
86 - scope: ReactiveScopeBlock,
87 - state: TemporariesUsedOutsideDefiningScope,
88 - ): void {
89 - this.scopes.push(scope.scope.id);
90 - this.traverseScope(scope, state);
91 - this.scopes.pop();
92 - }
93 -
94 - override visitInstruction(
95 - instruction: ReactiveInstruction,
96 - state: TemporariesUsedOutsideDefiningScope,
97 - ): void {
98 - // Visit all places first, then record temporaries which may need to be promoted
99 - this.traverseInstruction(instruction, state);
100 -
101 - const scope = this.scopes.at(-1);
102 - if (instruction.lvalue === null || scope === undefined) {
103 - return;
104 - }
105 - switch (instruction.value.kind) {
106 - case 'LoadLocal':
107 - case 'LoadContext':
108 - case 'PropertyLoad': {
109 - state.declarations.set(
110 - instruction.lvalue.identifier.declarationId,
111 - scope,
112 - );
113 - break;
114 - }
115 - default: {
116 - break;
117 - }
118 - }
119 - }
120 -
121 - override visitPlace(
122 - _id: InstructionId,
123 - place: Place,
124 - state: TemporariesUsedOutsideDefiningScope,
125 - ): void {
126 - const declaringScope = state.declarations.get(
127 - place.identifier.declarationId,
128 - );
129 - if (declaringScope === undefined) {
130 - return;
131 - }
132 - if (this.scopes.indexOf(declaringScope) === -1) {
133 - // Declaring scope is not active === used outside declaring scope
134 - state.usedOutsideDeclaringScope.add(place.identifier.declarationId);
135 - }
136 - }
137 -}
138 -
139 -type DeclMap = Map<DeclarationId, Decl>;
140 -type Decl = {
141 - id: InstructionId;
142 - scope: Stack<ScopeTraversalState>;
143 -};
144 -
145 -/**
146 - * TraversalState and PoisonState is used to track the poisoned state of a scope.
147 - *
148 - * A scope is poisoned when either of these conditions hold:
149 - * - one of its own nested blocks is a jump target (for break/continues)
150 - * - it is a outermost scope and contains a throw / return
151 - *
152 - * When a scope is poisoned, all dependencies (from instructions and inner scopes)
153 - * are added as conditionally accessed.
154 - */
155 -type ScopeTraversalState = {
156 - value: ReactiveScope;
157 - ownBlocks: Stack<BlockId>;
158 -};
159 -
160 -class PoisonState {
161 - poisonedBlocks: Set<BlockId> = new Set();
162 - poisonedScopes: Set<ScopeId> = new Set();
163 - isPoisoned: boolean = false;
164 -
165 - constructor(
166 - poisonedBlocks: Set<BlockId>,
167 - poisonedScopes: Set<ScopeId>,
168 - isPoisoned: boolean,
169 - ) {
170 - this.poisonedBlocks = poisonedBlocks;
171 - this.poisonedScopes = poisonedScopes;
172 - this.isPoisoned = isPoisoned;
173 - }
174 -
175 - clone(): PoisonState {
176 - return new PoisonState(
177 - new Set(this.poisonedBlocks),
178 - new Set(this.poisonedScopes),
179 - this.isPoisoned,
180 - );
181 - }
182 -
183 - take(other: PoisonState): PoisonState {
184 - const copy = new PoisonState(
185 - this.poisonedBlocks,
186 - this.poisonedScopes,
187 - this.isPoisoned,
188 - );
189 - this.poisonedBlocks = other.poisonedBlocks;
190 - this.poisonedScopes = other.poisonedScopes;
191 - this.isPoisoned = other.isPoisoned;
192 - return copy;
193 - }
194 -
195 - merge(
196 - others: Array<PoisonState>,
197 - currentScope: ScopeTraversalState | null,
198 - ): void {
199 - for (const other of others) {
200 - for (const id of other.poisonedBlocks) {
201 - this.poisonedBlocks.add(id);
202 - }
203 - for (const id of other.poisonedScopes) {
204 - this.poisonedScopes.add(id);
205 - }
206 - }
207 - this.#invalidate(currentScope);
208 - }
209 -
210 - #invalidate(currentScope: ScopeTraversalState | null): void {
211 - if (currentScope != null) {
212 - if (this.poisonedScopes.has(currentScope.value.id)) {
213 - this.isPoisoned = true;
214 - return;
215 - } else if (
216 - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId))
217 - ) {
218 - this.isPoisoned = true;
219 - return;
220 - }
221 - }
222 - this.isPoisoned = false;
223 - }
224 -
225 - /**
226 - * Mark a block or scope as poisoned and update the `isPoisoned` flag.
227 - *
228 - * @param targetBlock id of the block which ends non-linear control flow.
229 - * For a break/continue instruction, this is the target block.
230 - * Throw and return instructions have no target and will poison the earliest
231 - * active scope
232 - */
233 - addPoisonTarget(
234 - target: BlockId | null,
235 - activeScopes: Stack<ScopeTraversalState>,
236 - ): void {
237 - const currentScope = activeScopes.value;
238 - if (target == null && currentScope != null) {
239 - let cursor = activeScopes;
240 - while (true) {
241 - const next = cursor.pop();
242 - if (next.value == null) {
243 - const poisonedScope = cursor.value!.value.id;
244 - this.poisonedScopes.add(poisonedScope);
245 - if (poisonedScope === currentScope?.value.id) {
246 - this.isPoisoned = true;
247 - }
248 - break;
249 - } else {
250 - cursor = next;
251 - }
252 - }
253 - } else if (target != null) {
254 - this.poisonedBlocks.add(target);
255 - if (
256 - !this.isPoisoned &&
257 - currentScope?.ownBlocks.find(blockId => blockId === target)
258 - ) {
259 - this.isPoisoned = true;
260 - }
261 - }
262 - }
263 -
264 - /**
265 - * Invoked during traversal when a poisoned scope becomes inactive
266 - * @param id
267 - * @param currentScope
268 - */
269 - removeMaybePoisonedScope(
270 - id: ScopeId,
271 - currentScope: ScopeTraversalState | null,
272 - ): void {
273 - this.poisonedScopes.delete(id);
274 - this.#invalidate(currentScope);
275 - }
276 -
277 - removeMaybePoisonedBlock(
278 - id: BlockId,
279 - currentScope: ScopeTraversalState | null,
280 - ): void {
281 - this.poisonedBlocks.delete(id);
282 - this.#invalidate(currentScope);
283 - }
284 -}
285 -
286 -class Context {
287 - #temporariesUsedOutsideScope: Set<DeclarationId>;
288 - #declarations: DeclMap = new Map();
289 - #reassignments: Map<Identifier, Decl> = new Map();
290 - // Reactive dependencies used in the current reactive scope.
291 - #dependencies: ReactiveScopeDependencyTree =
292 - new ReactiveScopeDependencyTree();
293 - /*
294 - * We keep a sidemap for temporaries created by PropertyLoads, and do
295 - * not store any control flow (i.e. #inConditionalWithinScope) here.
296 - * - a ReactiveScope (A) containing a PropertyLoad may differ from the
297 - * ReactiveScope (B) that uses the produced temporary.
298 - * - codegen will inline these PropertyLoads back into scope (B)
299 - */
300 - #properties: Map<Identifier, ReactiveScopePropertyDependency> = new Map();
301 - #temporaries: Map<Identifier, Place> = new Map();
302 - #inConditionalWithinScope: boolean = false;
303 - /*
304 - * Reactive dependencies used unconditionally in the current conditional.
305 - * Composed of dependencies:
306 - * - directly accessed within block (added in visitDep)
307 - * - accessed by all cfg branches (added through promoteDeps)
308 - */
309 - #depsInCurrentConditional: ReactiveScopeDependencyTree =
310 - new ReactiveScopeDependencyTree();
311 - #scopes: Stack<ScopeTraversalState> = empty();
312 - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false);
313 -
314 - constructor(temporariesUsedOutsideScope: Set<DeclarationId>) {
315 - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope;
316 - }
317 -
318 - enter(scope: ReactiveScope, fn: () => void): Set<ReactiveScopeDependency> {
319 - // Save context of previous scope
320 - const prevInConditional = this.#inConditionalWithinScope;
321 - const previousDependencies = this.#dependencies;
322 - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this
323 - .isPoisoned
324 - ? this.#depsInCurrentConditional
325 - : null;
326 - if (prevDepsInConditional != null) {
327 - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree();
328 - }
329 -
330 - /*
331 - * Set context for new scope
332 - * A nested scope should add all deps it directly uses as its own
333 - * unconditional deps, regardless of whether the nested scope is itself
334 - * within a conditional
335 - */
336 - const scopedDependencies = new ReactiveScopeDependencyTree();
337 - this.#inConditionalWithinScope = false;
338 - this.#dependencies = scopedDependencies;
339 - this.#scopes = this.#scopes.push({
340 - value: scope,
341 - ownBlocks: empty(),
342 - });
343 - this.poisonState.isPoisoned = false;
344 -
345 - fn();
346 -
347 - // Restore context of previous scope
348 - this.#scopes = this.#scopes.pop();
349 - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value);
350 -
351 - this.#dependencies = previousDependencies;
352 - this.#inConditionalWithinScope = prevInConditional;
353 -
354 - // Derive minimal dependencies now, since next line may mutate scopedDependencies
355 - const minInnerScopeDependencies =
356 - scopedDependencies.deriveMinimalDependencies();
357 -
358 - /*
359 - * propagate dependencies upward using the same rules as normal dependency
360 - * collection. child scopes may have dependencies on values created within
361 - * the outer scope, which necessarily cannot be dependencies of the outer
362 - * scope
363 - */
364 - this.#dependencies.addDepsFromInnerScope(
365 - scopedDependencies,
366 - this.#inConditionalWithinScope || this.isPoisoned,
367 - this.#checkValidDependency.bind(this),
368 - );
369 -
370 - if (prevDepsInConditional != null) {
371 - // Outer scope is poisoned
372 - prevDepsInConditional.addDepsFromInnerScope(
373 - this.#depsInCurrentConditional,
374 - true,
375 - this.#checkValidDependency.bind(this),
376 - );
377 - this.#depsInCurrentConditional = prevDepsInConditional;
378 - }
379 -
380 - return minInnerScopeDependencies;
381 - }
382 -
383 - isUsedOutsideDeclaringScope(place: Place): boolean {
384 - return this.#temporariesUsedOutsideScope.has(
385 - place.identifier.declarationId,
386 - );
387 - }
388 -
389 - /*
390 - * Prints dependency tree to string for debugging.
391 - * @param includeAccesses
392 - * @returns string representation of DependencyTree
393 - */
394 - printDeps(includeAccesses: boolean = false): string {
395 - return this.#dependencies.printDeps(includeAccesses);
396 - }
397 -
398 - /*
399 - * We track and return unconditional accesses / deps within this conditional.
400 - * If an object property is always used (i.e. in every conditional path), we
401 - * want to promote it to an unconditional access / dependency.
402 - *
403 - * The caller of `enterConditional` is responsible determining for promotion.
404 - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results.
405 - *
406 - * e.g. we want to mark props.a.b as an unconditional dep here
407 - * if (foo(...)) {
408 - * access(props.a.b);
409 - * } else {
410 - * access(props.a.b);
411 - * }
412 - */
413 - enterConditional(fn: () => void): ReactiveScopeDependencyTree {
414 - const prevInConditional = this.#inConditionalWithinScope;
415 - const prevUncondAccessed = this.#depsInCurrentConditional;
416 - this.#inConditionalWithinScope = true;
417 - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree();
418 - fn();
419 - const result = this.#depsInCurrentConditional;
420 - this.#inConditionalWithinScope = prevInConditional;
421 - this.#depsInCurrentConditional = prevUncondAccessed;
422 - return result;
423 - }
424 -
425 - /*
426 - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps
427 - * tree. If a property is used in every CFG path, it is promoted to an
428 - * unconditional access / dependency here.
429 - * @param depsInConditionals
430 - */
431 - promoteDepsFromExhaustiveConditionals(
432 - depsInConditionals: Array<ReactiveScopeDependencyTree>,
433 - ): void {
434 - this.#dependencies.promoteDepsFromExhaustiveConditionals(
435 - depsInConditionals,
436 - );
437 - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals(
438 - depsInConditionals,
439 - );
440 - }
441 -
442 - /*
443 - * Records where a value was declared, and optionally, the scope where the value originated from.
444 - * This is later used to determine if a dependency should be added to a scope; if the current
445 - * scope we are visiting is the same scope where the value originates, it can't be a dependency
446 - * on itself.
447 - */
448 - declare(identifier: Identifier, decl: Decl): void {
449 - if (!this.#declarations.has(identifier.declarationId)) {
450 - this.#declarations.set(identifier.declarationId, decl);
451 - }
452 - this.#reassignments.set(identifier, decl);
453 - }
454 -
455 - declareTemporary(lvalue: Place, place: Place): void {
456 - this.#temporaries.set(lvalue.identifier, place);
457 - }
458 -
459 - resolveTemporary(place: Place): Place {
460 - return this.#temporaries.get(place.identifier) ?? place;
461 - }
462 -
463 - #getProperty(
464 - object: Place,
465 - property: string,
466 - optional: boolean,
467 - ): ReactiveScopePropertyDependency {
468 - const resolvedObject = this.resolveTemporary(object);
469 - const resolvedDependency = this.#properties.get(resolvedObject.identifier);
470 - let objectDependency: ReactiveScopePropertyDependency;
471 - /*
472 - * (1) Create the base property dependency as either a LoadLocal (from a temporary)
473 - * or a deep copy of an existing property dependency.
474 - */
475 - if (resolvedDependency === undefined) {
476 - objectDependency = {
477 - identifier: resolvedObject.identifier,
478 - path: [],
479 - };
480 - } else {
481 - objectDependency = {
482 - identifier: resolvedDependency.identifier,
483 - path: [...resolvedDependency.path],
484 - };
485 - }
486 -
487 - objectDependency.path.push({property, optional});
488 -
489 - return objectDependency;
490 - }
491 -
492 - declareProperty(
493 - lvalue: Place,
494 - object: Place,
495 - property: string,
496 - optional: boolean,
497 - ): void {
498 - const nextDependency = this.#getProperty(object, property, optional);
499 - this.#properties.set(lvalue.identifier, nextDependency);
500 - }
501 -
502 - // Checks if identifier is a valid dependency in the current scope
503 - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
504 - // ref.current access is not a valid dep
505 - if (
506 - isUseRefType(maybeDependency.identifier) &&
507 - maybeDependency.path.at(0)?.property === 'current'
508 - ) {
509 - return false;
510 - }
511 -
512 - // ref value is not a valid dep
513 - if (isRefValueType(maybeDependency.identifier)) {
514 - return false;
515 - }
516 -
517 - /*
518 - * object methods are not deps because they will be codegen'ed back in to
519 - * the object literal.
520 - */
521 - if (isObjectMethodType(maybeDependency.identifier)) {
522 - return false;
523 - }
524 -
525 - const identifier = maybeDependency.identifier;
526 - /*
527 - * If this operand is used in a scope, has a dynamic value, and was defined
528 - * before this scope, then its a dependency of the scope.
529 - */
530 - const currentDeclaration =
531 - this.#reassignments.get(identifier) ??
532 - this.#declarations.get(identifier.declarationId);
533 - const currentScope = this.currentScope.value?.value;
534 - return (
535 - currentScope != null &&
536 - currentDeclaration !== undefined &&
537 - currentDeclaration.id < currentScope.range.start &&
538 - (currentDeclaration.scope == null ||
539 - currentDeclaration.scope.value?.value !== currentScope)
540 - );
541 - }
542 -
543 - #isScopeActive(scope: ReactiveScope): boolean {
544 - if (this.#scopes === null) {
545 - return false;
546 - }
547 - return this.#scopes.find(state => state.value === scope);
548 - }
549 -
550 - get currentScope(): Stack<ScopeTraversalState> {
551 - return this.#scopes;
552 - }
553 -
554 - get isPoisoned(): boolean {
555 - return this.poisonState.isPoisoned;
556 - }
557 -
558 - visitOperand(place: Place): void {
559 - const resolved = this.resolveTemporary(place);
560 - /*
561 - * if this operand is a temporary created for a property load, try to resolve it to
562 - * the expanded Place. Fall back to using the operand as-is.
563 - */
564 -
565 - let dependency: ReactiveScopePropertyDependency = {
566 - identifier: resolved.identifier,
567 - path: [],
568 - };
569 - if (resolved.identifier.name === null) {
570 - const propertyDependency = this.#properties.get(resolved.identifier);
571 - if (propertyDependency !== undefined) {
572 - dependency = {...propertyDependency};
573 - }
574 - }
575 - this.visitDependency(dependency);
576 - }
577 -
578 - visitProperty(object: Place, property: string, optional: boolean): void {
579 - const nextDependency = this.#getProperty(object, property, optional);
580 - this.visitDependency(nextDependency);
581 - }
582 -
583 - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void {
584 - /*
585 - * Any value used after its originally defining scope has concluded must be added as an
586 - * output of its defining scope. Regardless of whether its a const or not,
587 - * some later code needs access to the value. If the current
588 - * scope we are visiting is the same scope where the value originates, it can't be a dependency
589 - * on itself.
590 - */
591 -
592 - /*
593 - * if originalDeclaration is undefined here, then this is a free var
594 - * (all other decls e.g. `let x;` should be initialized in BuildHIR)
595 - */
596 - const originalDeclaration = this.#declarations.get(
597 - maybeDependency.identifier.declarationId,
598 - );
599 - if (
600 - originalDeclaration !== undefined &&
601 - originalDeclaration.scope.value !== null
602 - ) {
603 - originalDeclaration.scope.each(scope => {
604 - if (
605 - !this.#isScopeActive(scope.value) &&
606 - // TODO LeaveSSA: key scope.declarations by DeclarationId
607 - !Iterable_some(
608 - scope.value.declarations.values(),
609 - decl =>
610 - decl.identifier.declarationId ===
611 - maybeDependency.identifier.declarationId,
612 - )
613 - ) {
614 - scope.value.declarations.set(maybeDependency.identifier.id, {
615 - identifier: maybeDependency.identifier,
616 - scope: originalDeclaration.scope.value!.value,
617 - });
618 - }
619 - });
620 - }
621 -
622 - if (this.#checkValidDependency(maybeDependency)) {
623 - const isPoisoned = this.isPoisoned;
624 - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned);
625 - /*
626 - * Add info about this dependency to the existing tree
627 - * We do not try to join/reduce dependencies here due to missing info
628 - */
629 - this.#dependencies.add(
630 - maybeDependency,
631 - this.#inConditionalWithinScope || isPoisoned,
632 - );
633 - }
634 - }
635 -
636 - /*
637 - * Record a variable that is declared in some other scope and that is being reassigned in the
638 - * current one as a {@link ReactiveScope.reassignments}
639 - */
640 - visitReassignment(place: Place): void {
641 - const currentScope = this.currentScope.value?.value;
642 - if (
643 - currentScope != null &&
644 - !Iterable_some(
645 - currentScope.reassignments,
646 - identifier =>
647 - identifier.declarationId === place.identifier.declarationId,
648 - ) &&
649 - this.#checkValidDependency({identifier: place.identifier, path: []})
650 - ) {
651 - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid
652 - currentScope.reassignments.add(place.identifier);
653 - }
654 - }
655 -
656 - pushLabeledBlock(id: BlockId): void {
657 - const currentScope = this.#scopes.value;
658 - if (currentScope != null) {
659 - currentScope.ownBlocks = currentScope.ownBlocks.push(id);
660 - }
661 - }
662 - popLabeledBlock(id: BlockId): void {
663 - const currentScope = this.#scopes.value;
664 - if (currentScope != null) {
665 - const last = currentScope.ownBlocks.value;
666 - currentScope.ownBlocks = currentScope.ownBlocks.pop();
667 -
668 - CompilerError.invariant(last != null && last === id, {
669 - reason: '[PropagateScopeDependencies] Misformed block stack',
670 - loc: GeneratedSource,
671 - });
672 - }
673 - this.poisonState.removeMaybePoisonedBlock(id, currentScope);
674 - }
675 -}
676 -
677 -class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
678 - env: Environment;
679 -
680 - constructor(env: Environment) {
681 - super();
682 - this.env = env;
683 - }
684 -
685 - override visitScope(scope: ReactiveScopeBlock, context: Context): void {
686 - const scopeDependencies = context.enter(scope.scope, () => {
687 - this.visitBlock(scope.instructions, context);
688 - });
689 - for (const candidateDep of scopeDependencies) {
690 - if (
691 - !Iterable_some(
692 - scope.scope.dependencies,
693 - existingDep =>
694 - existingDep.identifier.declarationId ===
695 - candidateDep.identifier.declarationId &&
696 - areEqualPaths(existingDep.path, candidateDep.path),
697 - )
698 - ) {
699 - scope.scope.dependencies.add(candidateDep);
700 - }
701 - }
702 - /*
703 - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments
704 - * see fixture ssa-cascading-eliminated-phis, note that we cache `x`
705 - * twice because its both a dep and a reassignment.
706 - *
707 - * for (const reassignment of scope.scope.reassignments) {
708 - * if (
709 - * Iterable_some(
710 - * scope.scope.dependencies.values(),
711 - * dep =>
712 - * dep.identifier.declarationId === reassignment.declarationId &&
713 - * dep.path.length === 0,
714 - * )
715 - * ) {
716 - * scope.scope.reassignments.delete(reassignment);
717 - * }
718 - * }
719 - */
720 - }
721 -
722 - override visitPrunedScope(
723 - scopeBlock: PrunedReactiveScopeBlock,
724 - context: Context,
725 - ): void {
726 - /*
727 - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its
728 - * declarations
729 - */
730 - const _scopeDepdencies = context.enter(scopeBlock.scope, () => {
731 - this.visitBlock(scopeBlock.instructions, context);
732 - });
733 - }
734 -
735 - override visitInstruction(
736 - instruction: ReactiveInstruction,
737 - context: Context,
738 - ): void {
739 - const {id, value, lvalue} = instruction;
740 - this.visitInstructionValue(context, id, value, lvalue);
741 - if (lvalue == null) {
742 - return;
743 - }
744 - context.declare(lvalue.identifier, {
745 - id,
746 - scope: context.currentScope,
747 - });
748 - }
749 -
750 - extractOptionalProperty(
751 - context: Context,
752 - optionalValue: ReactiveOptionalCallValue,
753 - lvalue: Place,
754 - ): {
755 - lvalue: Place;
756 - object: Place;
757 - property: string;
758 - optional: boolean;
759 - } | null {
760 - const sequence = optionalValue.value;
761 - CompilerError.invariant(sequence.kind === 'SequenceExpression', {
762 - reason: 'Expected OptionalExpression value to be a SequenceExpression',
763 - description: `Found a \`${sequence.kind}\``,
764 - loc: sequence.loc,
765 - });
766 - /**
767 - * Base case: inner `<variable> "?." <property>`
768 - *```
769 - * <lvalue> = OptionalExpression optional=true (`optionalValue` is here)
770 - * Sequence (`sequence` is here)
771 - * t0 = LoadLocal <variable>
772 - * Sequence
773 - * t1 = PropertyLoad t0 . <property>
774 - * LoadLocal t1
775 - * ```
776 - */
777 - if (
778 - sequence.instructions.length === 1 &&
779 - sequence.instructions[0].lvalue !== null &&
780 - sequence.instructions[0].value.kind === 'LoadLocal' &&
781 - sequence.instructions[0].value.place.identifier.name !== null &&
782 - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) &&
783 - sequence.value.kind === 'SequenceExpression' &&
784 - sequence.value.instructions.length === 1 &&
785 - sequence.value.instructions[0].value.kind === 'PropertyLoad' &&
786 - sequence.value.instructions[0].value.object.identifier.id ===
787 - sequence.instructions[0].lvalue.identifier.id &&
788 - sequence.value.instructions[0].lvalue !== null &&
789 - sequence.value.value.kind === 'LoadLocal' &&
790 - sequence.value.value.place.identifier.id ===
791 - sequence.value.instructions[0].lvalue.identifier.id
792 - ) {
793 - context.declareTemporary(
794 - sequence.instructions[0].lvalue,
795 - sequence.instructions[0].value.place,
796 - );
797 - const propertyLoad = sequence.value.instructions[0].value;
798 - return {
799 - lvalue,
800 - object: propertyLoad.object,
801 - property: propertyLoad.property,
802 - optional: optionalValue.optional,
803 - };
804 - }
805 - /**
806 - * Base case 2: inner `<variable> "." <property1> "?." <property2>
807 - * ```
808 - * <lvalue> = OptionalExpression optional=true (`optionalValue` is here)
809 - * Sequence (`sequence` is here)
810 - * t0 = Sequence
811 - * t1 = LoadLocal <variable>
812 - * ... // see note
813 - * PropertyLoad t1 . <property1>
814 - * [46] Sequence
815 - * t2 = PropertyLoad t0 . <property2>
816 - * [46] LoadLocal t2
817 - * ```
818 - *
819 - * Note that it's possible to have additional inner chained non-optional
820 - * property loads at "...", from an expression like `a?.b.c.d.e`. We could
821 - * expand to support this case by relaxing the check on the inner sequence
822 - * length, ensuring all instructions after the first LoadLocal are PropertyLoad
823 - * and then iterating to ensure that the lvalue of the previous is always
824 - * the object of the next PropertyLoad, w the final lvalue as the object
825 - * of the sequence.value's object.
826 - *
827 - * But this case is likely rare in practice, usually once you're optional
828 - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`).
829 - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't
830 - * seem worth it to optimize for that edge-case here.
831 - */
832 - if (
833 - sequence.instructions.length === 1 &&
834 - sequence.instructions[0].lvalue !== null &&
835 - sequence.instructions[0].value.kind === 'SequenceExpression' &&
836 - sequence.instructions[0].value.instructions.length === 1 &&
837 - sequence.instructions[0].value.instructions[0].lvalue !== null &&
838 - sequence.instructions[0].value.instructions[0].value.kind ===
839 - 'LoadLocal' &&
840 - sequence.instructions[0].value.instructions[0].value.place.identifier
841 - .name !== null &&
842 - !context.isUsedOutsideDeclaringScope(
843 - sequence.instructions[0].value.instructions[0].lvalue,
844 - ) &&
845 - sequence.instructions[0].value.value.kind === 'PropertyLoad' &&
846 - sequence.instructions[0].value.value.object.identifier.id ===
847 - sequence.instructions[0].value.instructions[0].lvalue.identifier.id &&
848 - sequence.value.kind === 'SequenceExpression' &&
849 - sequence.value.instructions.length === 1 &&
850 - sequence.value.instructions[0].lvalue !== null &&
851 - sequence.value.instructions[0].value.kind === 'PropertyLoad' &&
852 - sequence.value.instructions[0].value.object.identifier.id ===
853 - sequence.instructions[0].lvalue.identifier.id &&
854 - sequence.value.value.kind === 'LoadLocal' &&
855 - sequence.value.value.place.identifier.id ===
856 - sequence.value.instructions[0].lvalue.identifier.id
857 - ) {
858 - // LoadLocal <variable>
859 - context.declareTemporary(
860 - sequence.instructions[0].value.instructions[0].lvalue,
861 - sequence.instructions[0].value.instructions[0].value.place,
862 - );
863 - // PropertyLoad <variable> . <property1> (the inner non-optional property)
864 - context.declareProperty(
865 - sequence.instructions[0].lvalue,
866 - sequence.instructions[0].value.value.object,
867 - sequence.instructions[0].value.value.property,
868 - false,
869 - );
870 - const propertyLoad = sequence.value.instructions[0].value;
871 - return {
872 - lvalue,
873 - object: propertyLoad.object,
874 - property: propertyLoad.property,
875 - optional: optionalValue.optional,
876 - };
877 - }
878 -
879 - /**
880 - * Composed case:
881 - * - `<base-case> "." or "?." <property>`
882 - * - `<composed-case> "." or "?>" <property>`
883 - *
884 - * This case is convoluted, note how `t0` appears as an lvalue *twice*
885 - * and then is an operand of an intermediate LoadLocal and then the
886 - * object of the final PropertyLoad:
887 - *
888 - * ```
889 - * <lvalue> = OptionalExpression optional=false (`optionalValue` is here)
890 - * Sequence (`sequence` is here)
891 - * t0 = Sequence
892 - * t0 =
893 - * <nested>
894 - * LoadLocal t0
895 - * Sequence
896 - * t1 = PropertyLoad t0. <property>
897 - * LoadLocal t1
898 - * ```
899 - */
900 - if (
901 - sequence.instructions.length === 1 &&
902 - sequence.instructions[0].value.kind === 'SequenceExpression' &&
903 - sequence.instructions[0].value.instructions.length === 1 &&
904 - sequence.instructions[0].value.instructions[0].lvalue !== null &&
905 - sequence.instructions[0].value.instructions[0].value.kind ===
906 - 'OptionalExpression' &&
907 - sequence.instructions[0].value.value.kind === 'LoadLocal' &&
908 - sequence.instructions[0].value.value.place.identifier.id ===
909 - sequence.instructions[0].value.instructions[0].lvalue.identifier.id &&
910 - sequence.value.kind === 'SequenceExpression' &&
911 - sequence.value.instructions.length === 1 &&
912 - sequence.value.instructions[0].lvalue !== null &&
913 - sequence.value.instructions[0].value.kind === 'PropertyLoad' &&
914 - sequence.value.instructions[0].value.object.identifier.id ===
915 - sequence.instructions[0].value.value.place.identifier.id &&
916 - sequence.value.value.kind === 'LoadLocal' &&
917 - sequence.value.value.place.identifier.id ===
918 - sequence.value.instructions[0].lvalue.identifier.id
919 - ) {
920 - const {lvalue: innerLvalue, value: innerOptional} =
921 - sequence.instructions[0].value.instructions[0];
922 - const innerProperty = this.extractOptionalProperty(
923 - context,
924 - innerOptional,
925 - innerLvalue,
926 - );
927 - if (innerProperty === null) {
928 - return null;
929 - }
930 - context.declareProperty(
931 - innerProperty.lvalue,
932 - innerProperty.object,
933 - innerProperty.property,
934 - innerProperty.optional,
935 - );
936 - const propertyLoad = sequence.value.instructions[0].value;
937 - return {
938 - lvalue,
939 - object: propertyLoad.object,
940 - property: propertyLoad.property,
941 - optional: optionalValue.optional,
942 - };
943 - }
944 - return null;
945 - }
946 -
947 - visitOptionalExpression(
948 - context: Context,
949 - id: InstructionId,
950 - value: ReactiveOptionalCallValue,
951 - lvalue: Place | null,
952 - ): void {
953 - /**
954 - * If this is the first optional=true optional in a recursive OptionalExpression
955 - * subtree, we check to see if the subtree is of the form:
956 - * ```
957 - * NestedOptional =
958 - * `<variable> . / ?. <property>`
959 - * `<nested-optional> . / ?. <property>`
960 - * ```
961 - *
962 - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains
963 - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this
964 - * will return null and we'll go to the default handling below.
965 - *
966 - * If the tree does match the NestedOptional shape, then we'll have recorded
967 - * a sequence of declareProperty calls, and the final visitProperty call here
968 - * will record that optional chain as a dependency (since we know it's about
969 - * to be referenced via its lvalue which is non-null).
970 - */
971 - if (
972 - lvalue !== null &&
973 - value.optional &&
974 - this.env.config.enableOptionalDependencies
975 - ) {
976 - const inner = this.extractOptionalProperty(context, value, lvalue);
977 - if (inner !== null) {
978 - context.visitProperty(inner.object, inner.property, inner.optional);
979 - return;
980 - }
981 - }
982 -
983 - // Otherwise we treat everything after the optional as conditional
984 - const inner = value.value;
985 - /*
986 - * OptionalExpression value is a SequenceExpression where the instructions
987 - * represent the code prior to the `?` and the final value represents the
988 - * conditional code that follows.
989 - */
990 - CompilerError.invariant(inner.kind === 'SequenceExpression', {
991 - reason: 'Expected OptionalExpression value to be a SequenceExpression',
992 - description: `Found a \`${value.kind}\``,
993 - loc: value.loc,
994 - suggestions: null,
995 - });
996 - // Instructions are the unconditionally executed portion before the `?`
997 - for (const instr of inner.instructions) {
998 - this.visitInstruction(instr, context);
999 - }
1000 - // The final value is the conditional portion following the `?`
1001 - context.enterConditional(() => {
1002 - this.visitReactiveValue(context, id, inner.value, null);
1003 - });
1004 - }
1005 -
1006 - visitReactiveValue(
1007 - context: Context,
1008 - id: InstructionId,
1009 - value: ReactiveValue,
1010 - lvalue: Place | null,
1011 - ): void {
1012 - switch (value.kind) {
1013 - case 'OptionalExpression': {
1014 - this.visitOptionalExpression(context, id, value, lvalue);
1015 - break;
1016 - }
1017 - case 'LogicalExpression': {
1018 - this.visitReactiveValue(context, id, value.left, null);
1019 - context.enterConditional(() => {
1020 - this.visitReactiveValue(context, id, value.right, null);
1021 - });
1022 - break;
1023 - }
1024 - case 'ConditionalExpression': {
1025 - this.visitReactiveValue(context, id, value.test, null);
1026 -
1027 - const consequentDeps = context.enterConditional(() => {
1028 - this.visitReactiveValue(context, id, value.consequent, null);
1029 - });
1030 - const alternateDeps = context.enterConditional(() => {
1031 - this.visitReactiveValue(context, id, value.alternate, null);
1032 - });
1033 - context.promoteDepsFromExhaustiveConditionals([
1034 - consequentDeps,
1035 - alternateDeps,
1036 - ]);
1037 - break;
1038 - }
1039 - case 'SequenceExpression': {
1040 - for (const instr of value.instructions) {
1041 - this.visitInstruction(instr, context);
1042 - }
1043 - this.visitInstructionValue(context, id, value.value, null);
1044 - break;
1045 - }
1046 - case 'FunctionExpression': {
1047 - if (this.env.config.enableTreatFunctionDepsAsConditional) {
1048 - context.enterConditional(() => {
1049 - for (const operand of eachInstructionValueOperand(value)) {
1050 - context.visitOperand(operand);
1051 - }
1052 - });
1053 - } else {
1054 - for (const operand of eachInstructionValueOperand(value)) {
1055 - context.visitOperand(operand);
1056 - }
1057 - }
1058 - break;
1059 - }
1060 - case 'ReactiveFunctionValue': {
1061 - CompilerError.invariant(false, {
1062 - reason: `Unexpected ReactiveFunctionValue`,
1063 - loc: value.loc,
1064 - description: null,
1065 - suggestions: null,
1066 - });
1067 - }
1068 - default: {
1069 - for (const operand of eachInstructionValueOperand(value)) {
1070 - context.visitOperand(operand);
1071 - }
1072 - }
1073 - }
1074 - }
1075 -
1076 - visitInstructionValue(
1077 - context: Context,
1078 - id: InstructionId,
1079 - value: ReactiveValue,
1080 - lvalue: Place | null,
1081 - ): void {
1082 - if (value.kind === 'LoadLocal' && lvalue !== null) {
1083 - if (
1084 - value.place.identifier.name !== null &&
1085 - lvalue.identifier.name === null &&
1086 - !context.isUsedOutsideDeclaringScope(lvalue)
1087 - ) {
1088 - context.declareTemporary(lvalue, value.place);
1089 - } else {
1090 - context.visitOperand(value.place);
1091 - }
1092 - } else if (value.kind === 'PropertyLoad') {
1093 - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) {
1094 - context.declareProperty(lvalue, value.object, value.property, false);
1095 - } else {
1096 - context.visitProperty(value.object, value.property, false);
1097 - }
1098 - } else if (value.kind === 'StoreLocal') {
1099 - context.visitOperand(value.value);
1100 - if (value.lvalue.kind === InstructionKind.Reassign) {
1101 - context.visitReassignment(value.lvalue.place);
1102 - }
1103 - context.declare(value.lvalue.place.identifier, {
1104 - id,
1105 - scope: context.currentScope,
1106 - });
1107 - } else if (
1108 - value.kind === 'DeclareLocal' ||
1109 - value.kind === 'DeclareContext'
1110 - ) {
1111 - /*
1112 - * Some variables may be declared and never initialized. We need
1113 - * to retain (and hoist) these declarations if they are included
1114 - * in a reactive scope. One approach is to simply add all `DeclareLocal`s
1115 - * as scope declarations.
1116 - */
1117 -
1118 - /*
1119 - * We add context variable declarations here, not at `StoreContext`, since
1120 - * context Store / Loads are modeled as reads and mutates to the underlying
1121 - * variable reference (instead of through intermediate / inlined temporaries)
1122 - */
1123 - context.declare(value.lvalue.place.identifier, {
1124 - id,
1125 - scope: context.currentScope,
1126 - });
1127 - } else if (value.kind === 'Destructure') {
1128 - context.visitOperand(value.value);
1129 - for (const place of eachPatternOperand(value.lvalue.pattern)) {
1130 - if (value.lvalue.kind === InstructionKind.Reassign) {
1131 - context.visitReassignment(place);
1132 - }
1133 - context.declare(place.identifier, {
1134 - id,
1135 - scope: context.currentScope,
1136 - });
1137 - }
1138 - } else {
1139 - this.visitReactiveValue(context, id, value, lvalue);
1140 - }
1141 - }
1142 -
1143 - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void {
1144 - if (stmt.label != null) {
1145 - context.pushLabeledBlock(stmt.label.id);
1146 - }
1147 - const terminal = stmt.terminal;
1148 - switch (terminal.kind) {
1149 - case 'continue':
1150 - case 'break': {
1151 - context.poisonState.addPoisonTarget(
1152 - terminal.target,
1153 - context.currentScope,
1154 - );
1155 - break;
1156 - }
1157 - case 'throw':
1158 - case 'return': {
1159 - context.poisonState.addPoisonTarget(null, context.currentScope);
1160 - break;
1161 - }
1162 - }
1163 - }
1164 - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void {
1165 - if (stmt.label != null) {
1166 - context.popLabeledBlock(stmt.label.id);
1167 - }
1168 - }
1169 -
1170 - override visitTerminal(
1171 - stmt: ReactiveTerminalStatement,
1172 - context: Context,
1173 - ): void {
1174 - this.enterTerminal(stmt, context);
1175 - const terminal = stmt.terminal;
1176 - switch (terminal.kind) {
1177 - case 'break':
1178 - case 'continue': {
1179 - break;
1180 - }
1181 - case 'return': {
1182 - context.visitOperand(terminal.value);
1183 - break;
1184 - }
1185 - case 'throw': {
1186 - context.visitOperand(terminal.value);
1187 - break;
1188 - }
1189 - case 'for': {
1190 - this.visitReactiveValue(context, terminal.id, terminal.init, null);
1191 - this.visitReactiveValue(context, terminal.id, terminal.test, null);
1192 - context.enterConditional(() => {
1193 - this.visitBlock(terminal.loop, context);
1194 - if (terminal.update !== null) {
1195 - this.visitReactiveValue(
1196 - context,
1197 - terminal.id,
1198 - terminal.update,
1199 - null,
1200 - );
1201 - }
1202 - });
1203 - break;
1204 - }
1205 - case 'for-of': {
1206 - this.visitReactiveValue(context, terminal.id, terminal.init, null);
1207 - context.enterConditional(() => {
1208 - this.visitBlock(terminal.loop, context);
1209 - });
1210 - break;
1211 - }
1212 - case 'for-in': {
1213 - this.visitReactiveValue(context, terminal.id, terminal.init, null);
1214 - context.enterConditional(() => {
1215 - this.visitBlock(terminal.loop, context);
1216 - });
1217 - break;
1218 - }
1219 - case 'do-while': {
1220 - this.visitBlock(terminal.loop, context);
1221 - context.enterConditional(() => {
1222 - this.visitReactiveValue(context, terminal.id, terminal.test, null);
1223 - });
1224 - break;
1225 - }
1226 - case 'while': {
1227 - this.visitReactiveValue(context, terminal.id, terminal.test, null);
1228 - context.enterConditional(() => {
1229 - this.visitBlock(terminal.loop, context);
1230 - });
1231 - break;
1232 - }
1233 - case 'if': {
1234 - context.visitOperand(terminal.test);
1235 - const {consequent, alternate} = terminal;
1236 - /*
1237 - * Consequent and alternate branches are mutually exclusive,
1238 - * so we save and restore the poison state here.
1239 - */
1240 - const prevPoisonState = context.poisonState.clone();
1241 - const depsInIf = context.enterConditional(() => {
1242 - this.visitBlock(consequent, context);
1243 - });
1244 - if (alternate !== null) {
1245 - const ifPoisonState = context.poisonState.take(prevPoisonState);
1246 - const depsInElse = context.enterConditional(() => {
1247 - this.visitBlock(alternate, context);
1248 - });
1249 - context.poisonState.merge(
1250 - [ifPoisonState],
1251 - context.currentScope.value,
1252 - );
1253 - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]);
1254 - }
1255 - break;
1256 - }
1257 - case 'switch': {
1258 - context.visitOperand(terminal.test);
1259 - const isDefaultOnly =
1260 - terminal.cases.length === 1 && terminal.cases[0].test == null;
1261 - if (isDefaultOnly) {
1262 - const case_ = terminal.cases[0];
1263 - if (case_.block != null) {
1264 - this.visitBlock(case_.block, context);
1265 - break;
1266 - }
1267 - }
1268 - const depsInCases = [];
1269 - let foundDefault = false;
1270 - /**
1271 - * Switch branches are mutually exclusive
1272 - */
1273 - const prevPoisonState = context.poisonState.clone();
1274 - const mutExPoisonStates: Array<PoisonState> = [];
1275 - /*
1276 - * This can underestimate unconditional accesses due to the current
1277 - * CFG representation for fallthrough. This is safe. It only
1278 - * reduces granularity of dependencies.
1279 - */
1280 - for (const {test, block} of terminal.cases) {
1281 - if (test !== null) {
1282 - context.visitOperand(test);
1283 - } else {
1284 - foundDefault = true;
1285 - }
1286 - if (block !== undefined) {
1287 - mutExPoisonStates.push(
1288 - context.poisonState.take(prevPoisonState.clone()),
1289 - );
1290 - depsInCases.push(
1291 - context.enterConditional(() => {
1292 - this.visitBlock(block, context);
1293 - }),
1294 - );
1295 - }
1296 - }
1297 - if (foundDefault) {
1298 - context.promoteDepsFromExhaustiveConditionals(depsInCases);
1299 - }
1300 - context.poisonState.merge(
1301 - mutExPoisonStates,
1302 - context.currentScope.value,
1303 - );
1304 - break;
1305 - }
1306 - case 'label': {
1307 - this.visitBlock(terminal.block, context);
1308 - break;
1309 - }
1310 - case 'try': {
1311 - this.visitBlock(terminal.block, context);
1312 - this.visitBlock(terminal.handler, context);
1313 - break;
1314 - }
1315 - default: {
1316 - assertExhaustive(
1317 - terminal,
1318 - `Unexpected terminal kind \`${(terminal as any).kind}\``,
1319 - );
1320 - }
1321 - }
1322 - this.exitTerminal(stmt, context);
1323 - }
1324 -}
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts
-1
@@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh
17 export {printReactiveFunction} from './PrintReactiveFunction';
18 export {promoteUsedTemporaries} from './PromoteUsedTemporaries';
19 export {propagateEarlyReturns} from './PropagateEarlyReturns';
20 -export {propagateScopeDependencies} from './PropagateScopeDependencies';
20 export {pruneAllReactiveScopes} from './PruneAllReactiveScopes';
21 export {pruneHoistedContexts} from './PruneHoistedContexts';
22 export {pruneNonEscapingScopes} from './PruneNonEscapingScopes';
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md
+2 -2
@@ -58,7 +58,7 @@ function Component(t0) {
58 const $ = _c(5);
59 const { obj, isObjNull } = t0;
60 let t1;
61 - if ($[0] !== isObjNull || $[1] !== obj.prop) {
61 + if ($[0] !== isObjNull || $[1] !== obj) {
62 t1 = () => {
63 if (!isObjNull) {
64 return obj.prop;
@@ -67,7 +67,7 @@ function Component(t0) {
67 }
68 };
69 $[0] = isObjNull;
70 - $[1] = obj.prop;
70 + $[1] = obj;
71 $[2] = t1;
72 } else {
73 t1 = $[2];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md
+12 -4
@@ -38,16 +38,24 @@ import { identity } from "shared-runtime";
38 * try-catch block, as that might throw
39 */
40 function useFoo(maybeNullObject) {
41 - const $ = _c(2);
41 + const $ = _c(4);
42 let y;
43 - if ($[0] !== maybeNullObject.value.inner) {
43 + if ($[0] !== maybeNullObject) {
44 y = [];
45 try {
46 - y.push(identity(maybeNullObject.value.inner));
46 + let t0;
47 + if ($[2] !== maybeNullObject.value.inner) {
48 + t0 = identity(maybeNullObject.value.inner);
49 + $[2] = maybeNullObject.value.inner;
50 + $[3] = t0;
51 + } else {
52 + t0 = $[3];
53 + }
54 + y.push(t0);
55 } catch {
56 y.push("null");
57 }
50 - $[0] = maybeNullObject.value.inner;
58 + $[0] = maybeNullObject;
59 $[1] = y;
60 } else {
61 y = $[1];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md
+2 -2
@@ -37,7 +37,7 @@ function component(a, b) {
37 }
38 const y = t0;
39 let z;
40 - if ($[2] !== a || $[3] !== y.b) {
40 + if ($[2] !== a || $[3] !== y) {
41 z = { a };
42 const x = function () {
43 z.a = 2;
@@ -45,7 +45,7 @@ function component(a, b) {
45
46 x();
47 $[2] = a;
48 - $[3] = y.b;
48 + $[3] = y;
49 $[4] = z;
50 } else {
51 z = $[4];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md
+13 -5
@@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /**
33 * props.b *does* influence `a`
34 */
35 function Component(props) {
36 - const $ = _c(2);
36 + const $ = _c(5);
37 let a;
38 - if ($[0] !== props) {
38 + if (
39 + $[0] !== props.a ||
40 + $[1] !== props.b ||
41 + $[2] !== props.c ||
42 + $[3] !== props.d
43 + ) {
44 a = [];
45 a.push(props.a);
46 bb0: {
@@ -47,10 +52,13 @@ function Component(props) {
52 }
53
54 a.push(props.d);
50 - $[0] = props;
51 - $[1] = a;
55 + $[0] = props.a;
56 + $[1] = props.b;
57 + $[2] = props.c;
58 + $[3] = props.d;
59 + $[4] = a;
60 } else {
53 - a = $[1];
61 + a = $[4];
62 }
63 return a;
64 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md
+52 -26
@@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /**
70 * props.b does *not* influence `a`
71 */
72 function ComponentA(props) {
73 - const $ = _c(3);
73 + const $ = _c(5);
74 let a_DEBUG;
75 let t0;
76 - if ($[0] !== props) {
76 + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) {
77 t0 = Symbol.for("react.early_return_sentinel");
78 bb0: {
79 a_DEBUG = [];
@@ -85,12 +85,14 @@ function ComponentA(props) {
85
86 a_DEBUG.push(props.d);
87 }
88 - $[0] = props;
89 - $[1] = a_DEBUG;
90 - $[2] = t0;
88 + $[0] = props.a;
89 + $[1] = props.b;
90 + $[2] = props.d;
91 + $[3] = a_DEBUG;
92 + $[4] = t0;
93 } else {
92 - a_DEBUG = $[1];
93 - t0 = $[2];
94 + a_DEBUG = $[3];
95 + t0 = $[4];
96 }
97 if (t0 !== Symbol.for("react.early_return_sentinel")) {
98 return t0;
@@ -102,9 +104,14 @@ function ComponentA(props) {
104 * props.b *does* influence `a`
105 */
106 function ComponentB(props) {
105 - const $ = _c(2);
107 + const $ = _c(5);
108 let a;
107 - if ($[0] !== props) {
109 + if (
110 + $[0] !== props.a ||
111 + $[1] !== props.b ||
112 + $[2] !== props.c ||
113 + $[3] !== props.d
114 + ) {
115 a = [];
116 a.push(props.a);
117 if (props.b) {
@@ -112,10 +119,13 @@ function ComponentB(props) {
119 }
120
121 a.push(props.d);
115 - $[0] = props;
116 - $[1] = a;
122 + $[0] = props.a;
123 + $[1] = props.b;
124 + $[2] = props.c;
125 + $[3] = props.d;
126 + $[4] = a;
127 } else {
118 - a = $[1];
128 + a = $[4];
129 }
130 return a;
131 }
@@ -124,10 +134,15 @@ function ComponentB(props) {
134 * props.b *does* influence `a`, but only in a way that is never observable
135 */
136 function ComponentC(props) {
127 - const $ = _c(3);
137 + const $ = _c(6);
138 let a;
139 let t0;
130 - if ($[0] !== props) {
140 + if (
141 + $[0] !== props.a ||
142 + $[1] !== props.b ||
143 + $[2] !== props.c ||
144 + $[3] !== props.d
145 + ) {
146 t0 = Symbol.for("react.early_return_sentinel");
147 bb0: {
148 a = [];
@@ -140,12 +155,15 @@ function ComponentC(props) {
155
156 a.push(props.d);
157 }
143 - $[0] = props;
144 - $[1] = a;
145 - $[2] = t0;
158 + $[0] = props.a;
159 + $[1] = props.b;
160 + $[2] = props.c;
161 + $[3] = props.d;
162 + $[4] = a;
163 + $[5] = t0;
164 } else {
147 - a = $[1];
148 - t0 = $[2];
165 + a = $[4];
166 + t0 = $[5];
167 }
168 if (t0 !== Symbol.for("react.early_return_sentinel")) {
169 return t0;
@@ -157,10 +175,15 @@ function ComponentC(props) {
175 * props.b *does* influence `a`
176 */
177 function ComponentD(props) {
160 - const $ = _c(3);
178 + const $ = _c(6);
179 let a;
180 let t0;
163 - if ($[0] !== props) {
181 + if (
182 + $[0] !== props.a ||
183 + $[1] !== props.b ||
184 + $[2] !== props.c ||
185 + $[3] !== props.d
186 + ) {
187 t0 = Symbol.for("react.early_return_sentinel");
188 bb0: {
189 a = [];
@@ -173,12 +196,15 @@ function ComponentD(props) {
196
197 a.push(props.d);
198 }
176 - $[0] = props;
177 - $[1] = a;
178 - $[2] = t0;
199 + $[0] = props.a;
200 + $[1] = props.b;
201 + $[2] = props.c;
202 + $[3] = props.d;
203 + $[4] = a;
204 + $[5] = t0;
205 } else {
180 - a = $[1];
181 - t0 = $[2];
206 + a = $[4];
207 + t0 = $[5];
208 }
209 if (t0 !== Symbol.for("react.early_return_sentinel")) {
210 return t0;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md
+14 -10
@@ -36,9 +36,9 @@ function mayMutate() {}
36 ```javascript
37 import { c as _c } from "react/compiler-runtime";
38 function ComponentA(props) {
39 - const $ = _c(2);
39 + const $ = _c(4);
40 let t0;
41 - if ($[0] !== props) {
41 + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) {
42 const a = [];
43 const b = [];
44 if (b) {
@@ -49,18 +49,20 @@ function ComponentA(props) {
49 }
50
51 t0 = <Foo a={a} b={b} />;
52 - $[0] = props;
53 - $[1] = t0;
52 + $[0] = props.p0;
53 + $[1] = props.p1;
54 + $[2] = props.p2;
55 + $[3] = t0;
56 } else {
55 - t0 = $[1];
57 + t0 = $[3];
58 }
59 return t0;
60 }
61
62 function ComponentB(props) {
61 - const $ = _c(2);
63 + const $ = _c(4);
64 let t0;
63 - if ($[0] !== props) {
65 + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) {
66 const a = [];
67 const b = [];
68 if (mayMutate(b)) {
@@ -71,10 +73,12 @@ function ComponentB(props) {
73 }
74
75 t0 = <Foo a={a} b={b} />;
74 - $[0] = props;
75 - $[1] = t0;
76 + $[0] = props.p0;
77 + $[1] = props.p1;
78 + $[2] = props.p2;
79 + $[3] = t0;
80 } else {
77 - t0 = $[1];
81 + t0 = $[3];
82 }
83 return t0;
84 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md
+14 -12
@@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = {
31 ```javascript
32 import { c as _c } from "react/compiler-runtime";
33 function Component(props) {
34 - const $ = _c(5);
34 + const $ = _c(7);
35 let t0;
36 - if ($[0] !== props) {
36 + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) {
37 t0 = Symbol.for("react.early_return_sentinel");
38 bb0: {
39 const x = [];
@@ -41,12 +41,12 @@ function Component(props) {
41 x.push(props.a);
42 if (props.b) {
43 let t1;
44 - if ($[2] !== props.b) {
44 + if ($[4] !== props.b) {
45 t1 = [props.b];
46 - $[2] = props.b;
47 - $[3] = t1;
46 + $[4] = props.b;
47 + $[5] = t1;
48 } else {
49 - t1 = $[3];
49 + t1 = $[5];
50 }
51 const y = t1;
52 x.push(y);
@@ -58,20 +58,22 @@ function Component(props) {
58 break bb0;
59 } else {
60 let t1;
61 - if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
61 + if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
62 t1 = foo();
63 - $[4] = t1;
63 + $[6] = t1;
64 } else {
65 - t1 = $[4];
65 + t1 = $[6];
66 }
67 t0 = t1;
68 break bb0;
69 }
70 }
71 - $[0] = props;
72 - $[1] = t0;
71 + $[0] = props.a;
72 + $[1] = props.b;
73 + $[2] = props.cond;
74 + $[3] = t0;
75 } else {
74 - t0 = $[1];
76 + t0 = $[3];
77 }
78 if (t0 !== Symbol.for("react.early_return_sentinel")) {
79 return t0;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md
+11 -9
@@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime";
45 import { makeArray } from "shared-runtime";
46
47 function Component(props) {
48 - const $ = _c(4);
48 + const $ = _c(6);
49 let t0;
50 - if ($[0] !== props) {
50 + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) {
51 t0 = Symbol.for("react.early_return_sentinel");
52 bb0: {
53 const x = [];
@@ -57,21 +57,23 @@ function Component(props) {
57 break bb0;
58 } else {
59 let t1;
60 - if ($[2] !== props.b) {
60 + if ($[4] !== props.b) {
61 t1 = makeArray(props.b);
62 - $[2] = props.b;
63 - $[3] = t1;
62 + $[4] = props.b;
63 + $[5] = t1;
64 } else {
65 - t1 = $[3];
65 + t1 = $[5];
66 }
67 t0 = t1;
68 break bb0;
69 }
70 }
71 - $[0] = props;
72 - $[1] = t0;
71 + $[0] = props.a;
72 + $[1] = props.b;
73 + $[2] = props.cond;
74 + $[3] = t0;
75 } else {
74 - t0 = $[1];
76 + t0 = $[3];
77 }
78 if (t0 !== Symbol.for("react.early_return_sentinel")) {
79 return t0;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new
+50
@@ -0,0 +1,50 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
6 +import {ValidateMemoization} from 'shared-runtime';
7 +function Component(props) {
8 + const data = useMemo(() => {
9 + const x = [];
10 + x.push(props?.items);
11 + if (props.cond) {
12 + x.push(props?.items);
13 + }
14 + return x;
15 + }, [props?.items, props.cond]);
16 + return (
17 + <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
18 + );
19 +}
20 +
21 +```
22 +
23 +
24 +## Error
25 +
26 +```
27 + 2 | import {ValidateMemoization} from 'shared-runtime';
28 + 3 | function Component(props) {
29 +> 4 | const data = useMemo(() => {
30 + | ^^^^^^^
31 +> 5 | const x = [];
32 + | ^^^^^^^^^^^^^^^^^
33 +> 6 | x.push(props?.items);
34 + | ^^^^^^^^^^^^^^^^^
35 +> 7 | if (props.cond) {
36 + | ^^^^^^^^^^^^^^^^^
37 +> 8 | x.push(props?.items);
38 + | ^^^^^^^^^^^^^^^^^
39 +> 9 | }
40 + | ^^^^^^^^^^^^^^^^^
41 +> 10 | return x;
42 + | ^^^^^^^^^^^^^^^^^
43 +> 11 | }, [props?.items, props.cond]);
44 + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11)
45 + 12 | return (
46 + 13 | <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
47 + 14 | );
48 +```
49 +
50 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new
+50
@@ -0,0 +1,50 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
6 +import {ValidateMemoization} from 'shared-runtime';
7 +function Component(props) {
8 + const data = useMemo(() => {
9 + const x = [];
10 + x.push(props?.items);
11 + if (props.cond) {
12 + x.push(props.items);
13 + }
14 + return x;
15 + }, [props?.items, props.cond]);
16 + return (
17 + <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
18 + );
19 +}
20 +
21 +```
22 +
23 +
24 +## Error
25 +
26 +```
27 + 2 | import {ValidateMemoization} from 'shared-runtime';
28 + 3 | function Component(props) {
29 +> 4 | const data = useMemo(() => {
30 + | ^^^^^^^
31 +> 5 | const x = [];
32 + | ^^^^^^^^^^^^^^^^^
33 +> 6 | x.push(props?.items);
34 + | ^^^^^^^^^^^^^^^^^
35 +> 7 | if (props.cond) {
36 + | ^^^^^^^^^^^^^^^^^
37 +> 8 | x.push(props.items);
38 + | ^^^^^^^^^^^^^^^^^
39 +> 9 | }
40 + | ^^^^^^^^^^^^^^^^^
41 +> 10 | return x;
42 + | ^^^^^^^^^^^^^^^^^
43 +> 11 | }, [props?.items, props.cond]);
44 + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11)
45 + 12 | return (
46 + 13 | <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
47 + 14 | );
48 +```
49 +
50 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md
+1 -1
@@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = {
25 1 | function useFoo(props: {value: {x: string; y: string} | null}) {
26 2 | const value = props.value;
27 > 3 | return createArray(value?.x, value?.y)?.join(', ');
28 - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3)
28 + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3)
29 4 | }
30 5 |
31 6 | function createArray<T>(...args: Array<T>): Array<T> {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md
+2 -2
@@ -2,7 +2,7 @@
2 ## Input
3
4 ```javascript
5 -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
5 +// @enableTreatFunctionDepsAsConditional
6 import {Stringify} from 'shared-runtime';
7
8 function Component({props}) {
@@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = {
20 ## Code
21
22 ```javascript
23 -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
23 +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional
24 import { Stringify } from "shared-runtime";
25
26 function Component(t0) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx
+1 -1
@@ -1,4 +1,4 @@
1 -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
1 +// @enableTreatFunctionDepsAsConditional
2 import {Stringify} from 'shared-runtime';
3
4 function Component({props}) {
"b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr\342\200\223conditional-access.expect.md" renamed
+4 -4
@@ -2,7 +2,7 @@
2 ## Input
3
4 ```javascript
5 -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
5 +// @enableTreatFunctionDepsAsConditional
6 function Component(props) {
7 function getLength() {
8 return props.bar.length;
@@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = {
21 ## Code
22
23 ```javascript
24 -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
24 +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional
25 function Component(props) {
26 const $ = _c(5);
27 let t0;
28 - if ($[0] !== props) {
28 + if ($[0] !== props.bar) {
29 t0 = function getLength() {
30 return props.bar.length;
31 };
32 - $[0] = props;
32 + $[0] = props.bar;
33 $[1] = t0;
34 } else {
35 t0 = $[1];
"b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr\342\200\223conditional-access.js" renamed
+1 -1
@@ -1,4 +1,4 @@
1 -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
1 +// @enableTreatFunctionDepsAsConditional
2 function Component(props) {
3 function getLength() {
4 return props.bar.length;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md
+6 -5
@@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = {
26 ```javascript
27 import { c as _c } from "react/compiler-runtime";
28 function Component(props) {
29 - const $ = _c(2);
29 + const $ = _c(3);
30 let items;
31 - if ($[0] !== props) {
31 + if ($[0] !== props.a || $[1] !== props.cond) {
32 let t0;
33 if (props.cond) {
34 t0 = [];
@@ -38,10 +38,11 @@ function Component(props) {
38 items = t0;
39
40 items?.push(props.a);
41 - $[0] = props;
42 - $[1] = items;
41 + $[0] = props.a;
42 + $[1] = props.cond;
43 + $[2] = items;
44 } else {
44 - items = $[1];
45 + items = $[2];
46 }
47 return items;
48 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md
+2 -2
@@ -33,11 +33,11 @@ function useFoo(t0) {
33 const $ = _c(2);
34 const { a } = t0;
35 let x;
36 - if ($[0] !== a.b.c.d) {
36 + if ($[0] !== a.b.c.d.e) {
37 x = [];
38 x.push(a?.b.c?.d.e);
39 x.push(a.b?.c.d?.e);
40 - $[0] = a.b.c.d;
40 + $[0] = a.b.c.d.e;
41 $[1] = x;
42 } else {
43 x = $[1];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md
+6 -6
@@ -120,29 +120,29 @@ function useFoo(t0) {
120 }
121 const x = t1;
122 let t2;
123 - if ($[2] !== prop2?.inner) {
123 + if ($[2] !== prop2?.inner.value) {
124 t2 = identity(prop2?.inner.value)?.toString();
125 - $[2] = prop2?.inner;
125 + $[2] = prop2?.inner.value;
126 $[3] = t2;
127 } else {
128 t2 = $[3];
129 }
130 const y = t2;
131 let t3;
132 - if ($[4] !== prop3 || $[5] !== prop4) {
132 + if ($[4] !== prop3 || $[5] !== prop4?.inner) {
133 t3 = prop3?.fn(prop4?.inner.value).toString();
134 $[4] = prop3;
135 - $[5] = prop4;
135 + $[5] = prop4?.inner;
136 $[6] = t3;
137 } else {
138 t3 = $[6];
139 }
140 const z = t3;
141 let t4;
142 - if ($[7] !== prop5 || $[8] !== prop6) {
142 + if ($[7] !== prop5 || $[8] !== prop6?.inner) {
143 t4 = prop5?.fn(prop6?.inner.value)?.toString();
144 $[7] = prop5;
145 - $[8] = prop6;
145 + $[8] = prop6?.inner;
146 $[9] = t4;
147 } else {
148 t4 = $[9];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md
+6 -5
@@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime";
29 import { makeObject_Primitives } from "shared-runtime";
30
31 function Component(props) {
32 - const $ = _c(2);
32 + const $ = _c(3);
33 let t0;
34 - if ($[0] !== props) {
34 + if ($[0] !== props.cond || $[1] !== props.value) {
35 t0 = Symbol.for("react.early_return_sentinel");
36 bb0: {
37 const object = makeObject_Primitives();
@@ -45,10 +45,11 @@ function Component(props) {
45 break bb0;
46 }
47 }
48 - $[0] = props;
49 - $[1] = t0;
48 + $[0] = props.cond;
49 + $[1] = props.value;
50 + $[2] = t0;
51 } else {
51 - t0 = $[1];
52 + t0 = $[2];
53 }
54 if (t0 !== Symbol.for("react.early_return_sentinel")) {
55 return t0;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted
-74
@@ -1,74 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
6 -import {ValidateMemoization} from 'shared-runtime';
7 -function Component(props) {
8 - const data = useMemo(() => {
9 - const x = [];
10 - x.push(props?.items);
11 - if (props.cond) {
12 - x.push(props?.items);
13 - }
14 - return x;
15 - }, [props?.items, props.cond]);
16 - return (
17 - <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
18 - );
19 -}
20 -
21 -```
22 -
23 -## Code
24 -
25 -```javascript
26 -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
27 -import { ValidateMemoization } from "shared-runtime";
28 -function Component(props) {
29 - const $ = _c(9);
30 -
31 - props?.items;
32 - let t0;
33 - let x;
34 - if ($[0] !== props?.items || $[1] !== props.cond) {
35 - x = [];
36 - x.push(props?.items);
37 - if (props.cond) {
38 - x.push(props?.items);
39 - }
40 - $[0] = props?.items;
41 - $[1] = props.cond;
42 - $[2] = x;
43 - } else {
44 - x = $[2];
45 - }
46 - t0 = x;
47 - const data = t0;
48 -
49 - const t1 = props?.items;
50 - let t2;
51 - if ($[3] !== props.cond || $[4] !== t1) {
52 - t2 = [t1, props.cond];
53 - $[3] = props.cond;
54 - $[4] = t1;
55 - $[5] = t2;
56 - } else {
57 - t2 = $[5];
58 - }
59 - let t3;
60 - if ($[6] !== data || $[7] !== t2) {
61 - t3 = <ValidateMemoization inputs={t2} output={data} />;
62 - $[6] = data;
63 - $[7] = t2;
64 - $[8] = t3;
65 - } else {
66 - t3 = $[8];
67 - }
68 - return t3;
69 -}
70 -
71 -```
72 -
73 -### Eval output
74 -(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted
-74
@@ -1,74 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
6 -import {ValidateMemoization} from 'shared-runtime';
7 -function Component(props) {
8 - const data = useMemo(() => {
9 - const x = [];
10 - x.push(props?.items);
11 - if (props.cond) {
12 - x.push(props.items);
13 - }
14 - return x;
15 - }, [props?.items, props.cond]);
16 - return (
17 - <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
18 - );
19 -}
20 -
21 -```
22 -
23 -## Code
24 -
25 -```javascript
26 -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
27 -import { ValidateMemoization } from "shared-runtime";
28 -function Component(props) {
29 - const $ = _c(9);
30 -
31 - props?.items;
32 - let t0;
33 - let x;
34 - if ($[0] !== props?.items || $[1] !== props.cond) {
35 - x = [];
36 - x.push(props?.items);
37 - if (props.cond) {
38 - x.push(props.items);
39 - }
40 - $[0] = props?.items;
41 - $[1] = props.cond;
42 - $[2] = x;
43 - } else {
44 - x = $[2];
45 - }
46 - t0 = x;
47 - const data = t0;
48 -
49 - const t1 = props?.items;
50 - let t2;
51 - if ($[3] !== props.cond || $[4] !== t1) {
52 - t2 = [t1, props.cond];
53 - $[3] = props.cond;
54 - $[4] = t1;
55 - $[5] = t2;
56 - } else {
57 - t2 = $[5];
58 - }
59 - let t3;
60 - if ($[6] !== data || $[7] !== t2) {
61 - t3 = <ValidateMemoization inputs={t2} output={data} />;
62 - $[6] = data;
63 - $[7] = t2;
64 - $[8] = t3;
65 - } else {
66 - t3 = $[8];
67 - }
68 - return t3;
69 -}
70 -
71 -```
72 -
73 -### Eval output
74 -(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md
+12 -10
@@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = {
30 ```javascript
31 import { c as _c } from "react/compiler-runtime";
32 function Component(props) {
33 - const $ = _c(4);
33 + const $ = _c(6);
34 let t0;
35 let y;
36 - if ($[0] !== props) {
36 + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) {
37 t0 = Symbol.for("react.early_return_sentinel");
38 bb0: {
39 const x = [];
@@ -43,11 +43,11 @@ function Component(props) {
43 break bb0;
44 } else {
45 let t1;
46 - if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
46 + if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
47 t1 = foo();
48 - $[3] = t1;
48 + $[5] = t1;
49 } else {
50 - t1 = $[3];
50 + t1 = $[5];
51 }
52 y = t1;
53 if (props.b) {
@@ -56,12 +56,14 @@ function Component(props) {
56 }
57 }
58 }
59 - $[0] = props;
60 - $[1] = t0;
61 - $[2] = y;
59 + $[0] = props.a;
60 + $[1] = props.b;
61 + $[2] = props.cond;
62 + $[3] = t0;
63 + $[4] = y;
64 } else {
63 - t0 = $[1];
64 - y = $[2];
65 + t0 = $[3];
66 + y = $[4];
67 }
68 if (t0 !== Symbol.for("react.early_return_sentinel")) {
69 return t0;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md
+13 -5
@@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime";
49 import { makeArray } from "shared-runtime";
50
51 function Component(props) {
52 - const $ = _c(3);
52 + const $ = _c(6);
53 let t0;
54 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
55 t0 = {};
@@ -59,7 +59,12 @@ function Component(props) {
59 }
60 const x = t0;
61 let t1;
62 - if ($[1] !== props) {
62 + if (
63 + $[1] !== props.cond ||
64 + $[2] !== props.cond2 ||
65 + $[3] !== props.value ||
66 + $[4] !== props.value2
67 + ) {
68 let y;
69 if (props.cond) {
70 if (props.cond2) {
@@ -74,10 +79,13 @@ function Component(props) {
79 y.push(x);
80
81 t1 = [x, y];
77 - $[1] = props;
78 - $[2] = t1;
82 + $[1] = props.cond;
83 + $[2] = props.cond2;
84 + $[3] = props.value;
85 + $[4] = props.value2;
86 + $[5] = t1;
87 } else {
80 - t1 = $[2];
88 + t1 = $[5];
89 }
90 return t1;
91 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md
+6 -5
@@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
36 ```javascript
37 import { c as _c } from "react/compiler-runtime";
38 function Component(props) {
39 - const $ = _c(3);
39 + const $ = _c(4);
40 let t0;
41 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
42 t0 = {};
@@ -46,7 +46,7 @@ function Component(props) {
46 }
47 const x = t0;
48 let t1;
49 - if ($[1] !== props) {
49 + if ($[1] !== props.cond || $[2] !== props.value) {
50 let y;
51 if (props.cond) {
52 y = [props.value];
@@ -57,10 +57,11 @@ function Component(props) {
57 y.push(x);
58
59 t1 = [x, y];
60 - $[1] = props;
61 - $[2] = t1;
60 + $[1] = props.cond;
61 + $[2] = props.value;
62 + $[3] = t1;
63 } else {
63 - t1 = $[2];
64 + t1 = $[3];
65 }
66 return t1;
67 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md
+6 -5
@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
32 ```javascript
33 import { c as _c } from "react/compiler-runtime"; // @debug
34 function Component(props) {
35 - const $ = _c(3);
35 + const $ = _c(4);
36 let t0;
37 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
38 t0 = {};
@@ -42,7 +42,7 @@ function Component(props) {
42 }
43 const x = t0;
44 let t1;
45 - if ($[1] !== props) {
45 + if ($[1] !== props.a || $[2] !== props.cond) {
46 let y;
47 if (props.cond) {
48 y = {};
@@ -53,10 +53,11 @@ function Component(props) {
53 y.x = x;
54
55 t1 = [x, y];
56 - $[1] = props;
57 - $[2] = t1;
56 + $[1] = props.a;
57 + $[2] = props.cond;
58 + $[3] = t1;
59 } else {
59 - t1 = $[2];
60 + t1 = $[3];
61 }
62 return t1;
63 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new
+50
@@ -0,0 +1,50 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {useCallback} from 'react';
7 +
8 +function Component({propA, propB}) {
9 + return useCallback(() => {
10 + if (propA) {
11 + return {
12 + value: propB.x.y,
13 + };
14 + }
15 + }, [propA, propB.x.y]);
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: Component,
20 + params: [{propA: 1, propB: {x: {y: []}}}],
21 +};
22 +
23 +```
24 +
25 +
26 +## Error
27 +
28 +```
29 + 3 |
30 + 4 | function Component({propA, propB}) {
31 +> 5 | return useCallback(() => {
32 + | ^^^^^^^
33 +> 6 | if (propA) {
34 + | ^^^^^^^^^^^^^^^^
35 +> 7 | return {
36 + | ^^^^^^^^^^^^^^^^
37 +> 8 | value: propB.x.y,
38 + | ^^^^^^^^^^^^^^^^
39 +> 9 | };
40 + | ^^^^^^^^^^^^^^^^
41 +> 10 | }
42 + | ^^^^^^^^^^^^^^^^
43 +> 11 | }, [propA, propB.x.y]);
44 + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11)
45 + 12 | }
46 + 13 |
47 + 14 | export const FIXTURE_ENTRYPOINT = {
48 +```
49 +
50 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new
+59
@@ -0,0 +1,59 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {useCallback} from 'react';
7 +import {identity, mutate} from 'shared-runtime';
8 +
9 +function useHook(propA, propB) {
10 + return useCallback(() => {
11 + const x = {};
12 + if (identity(null) ?? propA.a) {
13 + mutate(x);
14 + return {
15 + value: propB.x.y,
16 + };
17 + }
18 + }, [propA.a, propB.x.y]);
19 +}
20 +
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: useHook,
23 + params: [{a: 1}, {x: {y: 3}}],
24 +};
25 +
26 +```
27 +
28 +
29 +## Error
30 +
31 +```
32 + 4 |
33 + 5 | function useHook(propA, propB) {
34 +> 6 | return useCallback(() => {
35 + | ^^^^^^^
36 +> 7 | const x = {};
37 + | ^^^^^^^^^^^^^^^^^
38 +> 8 | if (identity(null) ?? propA.a) {
39 + | ^^^^^^^^^^^^^^^^^
40 +> 9 | mutate(x);
41 + | ^^^^^^^^^^^^^^^^^
42 +> 10 | return {
43 + | ^^^^^^^^^^^^^^^^^
44 +> 11 | value: propB.x.y,
45 + | ^^^^^^^^^^^^^^^^^
46 +> 12 | };
47 + | ^^^^^^^^^^^^^^^^^
48 +> 13 | }
49 + | ^^^^^^^^^^^^^^^^^
50 +> 14 | }, [propA.a, propB.x.y]);
51 + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
52 +
53 +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
54 + 15 | }
55 + 16 |
56 + 17 | export const FIXTURE_ENTRYPOINT = {
57 +```
58 +
59 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md
+2
@@ -44,6 +44,8 @@ function Component({propA, propB}) {
44 | ^^^^^^^^^^^^^^^^^
45 > 14 | }, [propA?.a, propB.x.y]);
46 | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
47 +
48 +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
49 15 | }
50 16 |
51 ```
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted
-58
@@ -1,58 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validatePreserveExistingMemoizationGuarantees
6 -import {useCallback} from 'react';
7 -
8 -function Component({propA, propB}) {
9 - return useCallback(() => {
10 - if (propA) {
11 - return {
12 - value: propB.x.y,
13 - };
14 - }
15 - }, [propA, propB.x.y]);
16 -}
17 -
18 -export const FIXTURE_ENTRYPOINT = {
19 - fn: Component,
20 - params: [{propA: 1, propB: {x: {y: []}}}],
21 -};
22 -
23 -```
24 -
25 -## Code
26 -
27 -```javascript
28 -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
29 -import { useCallback } from "react";
30 -
31 -function Component(t0) {
32 - const $ = _c(3);
33 - const { propA, propB } = t0;
34 - let t1;
35 - if ($[0] !== propA || $[1] !== propB.x.y) {
36 - t1 = () => {
37 - if (propA) {
38 - return { value: propB.x.y };
39 - }
40 - };
41 - $[0] = propA;
42 - $[1] = propB.x.y;
43 - $[2] = t1;
44 - } else {
45 - t1 = $[2];
46 - }
47 - return t1;
48 -}
49 -
50 -export const FIXTURE_ENTRYPOINT = {
51 - fn: Component,
52 - params: [{ propA: 1, propB: { x: { y: [] } } }],
53 -};
54 -
55 -```
56 -
57 -### Eval output
58 -(kind: ok) "[[ function params=0 ]]"
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted
-63
@@ -1,63 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validatePreserveExistingMemoizationGuarantees
6 -import {useCallback} from 'react';
7 -import {identity, mutate} from 'shared-runtime';
8 -
9 -function useHook(propA, propB) {
10 - return useCallback(() => {
11 - const x = {};
12 - if (identity(null) ?? propA.a) {
13 - mutate(x);
14 - return {
15 - value: propB.x.y,
16 - };
17 - }
18 - }, [propA.a, propB.x.y]);
19 -}
20 -
21 -export const FIXTURE_ENTRYPOINT = {
22 - fn: useHook,
23 - params: [{a: 1}, {x: {y: 3}}],
24 -};
25 -
26 -```
27 -
28 -## Code
29 -
30 -```javascript
31 -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
32 -import { useCallback } from "react";
33 -import { identity, mutate } from "shared-runtime";
34 -
35 -function useHook(propA, propB) {
36 - const $ = _c(3);
37 - let t0;
38 - if ($[0] !== propA.a || $[1] !== propB.x.y) {
39 - t0 = () => {
40 - const x = {};
41 - if (identity(null) ?? propA.a) {
42 - mutate(x);
43 - return { value: propB.x.y };
44 - }
45 - };
46 - $[0] = propA.a;
47 - $[1] = propB.x.y;
48 - $[2] = t0;
49 - } else {
50 - t0 = $[2];
51 - }
52 - return t0;
53 -}
54 -
55 -export const FIXTURE_ENTRYPOINT = {
56 - fn: useHook,
57 - params: [{ a: 1 }, { x: { y: 3 } }],
58 -};
59 -
60 -```
61 -
62 -### Eval output
63 -(kind: ok) "[[ function params=0 ]]"
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md
+2 -2
@@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime";
15 function Component(props) {
16 const $ = _c(2);
17 let t0;
18 - if ($[0] !== props.post.feedback.comments) {
18 + if ($[0] !== props.post.feedback.comments?.edges) {
19 t0 = props.post.feedback.comments?.edges?.map(render);
20 - $[0] = props.post.feedback.comments;
20 + $[0] = props.post.feedback.comments?.edges;
21 $[1] = t0;
22 } else {
23 t0 = $[1];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md
+2 -2
@@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime";
23 function Component(props) {
24 const $ = _c(2);
25 let t0;
26 - if ($[0] !== props.str) {
26 + if ($[0] !== props) {
27 t0 = () => {
28 let str;
29 if (arguments.length) {
@@ -34,7 +34,7 @@ function Component(props) {
34
35 global.log(str);
36 };
37 - $[0] = props.str;
37 + $[0] = props;
38 $[1] = t0;
39 } else {
40 t0 = $[1];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md
+2 -2
@@ -38,7 +38,7 @@ function Foo(t0) {
38 const $ = _c(3);
39 const { a, shouldReadA } = t0;
40 let t1;
41 - if ($[0] !== a.b.c || $[1] !== shouldReadA) {
41 + if ($[0] !== a || $[1] !== shouldReadA) {
42 t1 = (
43 <Stringify
44 fn={() => {
@@ -50,7 +50,7 @@ function Foo(t0) {
50 shouldInvokeFns={true}
51 />
52 );
53 - $[0] = a.b.c;
53 + $[0] = a;
54 $[1] = shouldReadA;
55 $[2] = t1;
56 } else {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md
+2 -2
@@ -65,12 +65,12 @@ function useFoo(t0) {
65 const $ = _c(2);
66 const { screen } = t0;
67 let t1;
68 - if ($[0] !== screen?.title_text) {
68 + if ($[0] !== screen) {
69 t1 =
70 screen?.title_text != null
71 ? "(not null)"
72 : identity({ title: screen.title_text });
73 - $[0] = screen?.title_text;
73 + $[0] = screen;
74 $[1] = t1;
75 } else {
76 t1 = $[1];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md
+4 -11
@@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization,
61 import { CONST_TRUE, setProperty } from "shared-runtime";
62
63 function useJoinCondDepsInUncondScopes(props) {
64 - const $ = _c(4);
64 + const $ = _c(2);
65 let t0;
66 if ($[0] !== props.a.b) {
67 const y = {};
68 - let x;
69 - if ($[2] !== props) {
70 - x = {};
71 - if (CONST_TRUE) {
72 - setProperty(x, props.a.b);
73 - }
74 - $[2] = props;
75 - $[3] = x;
76 - } else {
77 - x = $[3];
68 + const x = {};
69 + if (CONST_TRUE) {
70 + setProperty(x, props.a.b);
71 }
72
73 setProperty(y, props.a.b);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md
+6 -5
@@ -34,19 +34,20 @@ import { identity } from "shared-runtime";
34
35 // and promote it to an unconditional dependency.
36 function usePromoteUnconditionalAccessToDependency(props, other) {
37 - const $ = _c(3);
37 + const $ = _c(4);
38 let x;
39 - if ($[0] !== other || $[1] !== props.a) {
39 + if ($[0] !== other || $[1] !== props.a.a.a || $[2] !== props.a.b) {
40 x = {};
41 x.a = props.a.a.a;
42 if (identity(other)) {
43 x.c = props.a.b.c;
44 }
45 $[0] = other;
46 - $[1] = props.a;
47 - $[2] = x;
46 + $[1] = props.a.a.a;
47 + $[2] = props.a.b;
48 + $[3] = x;
49 } else {
49 - x = $[2];
50 + x = $[3];
51 }
52 return x;
53 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md
+17 -8
@@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = {
36 ```javascript
37 import { c as _c } from "react/compiler-runtime";
38 function Component(props) {
39 - const $ = _c(4);
39 + const $ = _c(7);
40 let x = 0;
41 let values;
42 - if ($[0] !== props || $[1] !== x) {
42 + if (
43 + $[0] !== props.a ||
44 + $[1] !== props.b ||
45 + $[2] !== props.c ||
46 + $[3] !== props.d ||
47 + $[4] !== x
48 + ) {
49 values = [];
50 const y = props.a || props.b;
51 values.push(y);
@@ -53,13 +59,16 @@ function Component(props) {
59 }
60
61 values.push(x);
56 - $[0] = props;
57 - $[1] = x;
58 - $[2] = values;
59 - $[3] = x;
62 + $[0] = props.a;
63 + $[1] = props.b;
64 + $[2] = props.c;
65 + $[3] = props.d;
66 + $[4] = x;
67 + $[5] = values;
68 + $[6] = x;
69 } else {
61 - values = $[2];
62 - x = $[3];
70 + values = $[5];
71 + x = $[6];
72 }
73 return values;
74 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md
+6 -5
@@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime";
39 import { Stringify } from "shared-runtime";
40
41 function Component(props) {
42 - const $ = _c(2);
42 + const $ = _c(3);
43 let t0;
44 - if ($[0] !== props) {
44 + if ($[0] !== props.p0 || $[1] !== props.p1) {
45 const x = [];
46 let y;
47 if (props.p0) {
@@ -55,10 +55,11 @@ function Component(props) {
55 {y}
56 </Stringify>
57 );
58 - $[0] = props;
59 - $[1] = t0;
58 + $[0] = props.p0;
59 + $[1] = props.p1;
60 + $[2] = t0;
61 } else {
61 - t0 = $[1];
62 + t0 = $[2];
63 }
64 return t0;
65 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md
+7 -5
@@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime";
31 import { mutate } from "shared-runtime";
32
33 function useFoo(props) {
34 - const $ = _c(2);
34 + const $ = _c(4);
35 let x;
36 - if ($[0] !== props) {
36 + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
37 x = [];
38 x.push(props.bar);
39 props.cond ? (([x] = [[]]), x.push(props.foo)) : null;
40 mutate(x);
41 - $[0] = props;
42 - $[1] = x;
41 + $[0] = props.bar;
42 + $[1] = props.cond;
43 + $[2] = props.foo;
44 + $[3] = x;
45 } else {
44 - x = $[1];
46 + x = $[3];
47 }
48 return x;
49 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md
+6 -5
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
26 ```javascript
27 import { c as _c } from "react/compiler-runtime";
28 function useFoo(props) {
29 - const $ = _c(4);
29 + const $ = _c(5);
30 let x;
31 if ($[0] !== props.bar) {
32 x = [];
@@ -36,12 +36,13 @@ function useFoo(props) {
36 } else {
37 x = $[1];
38 }
39 - if ($[2] !== props) {
39 + if ($[2] !== props.cond || $[3] !== props.foo) {
40 props.cond ? (([x] = [[]]), x.push(props.foo)) : null;
41 - $[2] = props;
42 - $[3] = x;
41 + $[2] = props.cond;
42 + $[3] = props.foo;
43 + $[4] = x;
44 } else {
44 - x = $[3];
45 + x = $[4];
46 }
47 return x;
48 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md
+7 -5
@@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime";
31 import { mutate } from "shared-runtime";
32
33 function useFoo(props) {
34 - const $ = _c(2);
34 + const $ = _c(4);
35 let x;
36 - if ($[0] !== props) {
36 + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
37 x = [];
38 x.push(props.bar);
39 props.cond ? ((x = []), x.push(props.foo)) : null;
40 mutate(x);
41 - $[0] = props;
42 - $[1] = x;
41 + $[0] = props.bar;
42 + $[1] = props.cond;
43 + $[2] = props.foo;
44 + $[3] = x;
45 } else {
44 - x = $[1];
46 + x = $[3];
47 }
48 return x;
49 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md
+6 -5
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
26 ```javascript
27 import { c as _c } from "react/compiler-runtime";
28 function useFoo(props) {
29 - const $ = _c(4);
29 + const $ = _c(5);
30 let x;
31 if ($[0] !== props.bar) {
32 x = [];
@@ -36,12 +36,13 @@ function useFoo(props) {
36 } else {
37 x = $[1];
38 }
39 - if ($[2] !== props) {
39 + if ($[2] !== props.cond || $[3] !== props.foo) {
40 props.cond ? ((x = []), x.push(props.foo)) : null;
41 - $[2] = props;
42 - $[3] = x;
41 + $[2] = props.cond;
42 + $[3] = props.foo;
43 + $[4] = x;
44 } else {
44 - x = $[3];
45 + x = $[4];
46 }
47 return x;
48 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md
+7 -5
@@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = {
31 import { c as _c } from "react/compiler-runtime";
32 import { arrayPush } from "shared-runtime";
33 function useFoo(props) {
34 - const $ = _c(2);
34 + const $ = _c(4);
35 let x;
36 - if ($[0] !== props) {
36 + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
37 x = [];
38 x.push(props.bar);
39 props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar));
40 arrayPush(x, 4);
41 - $[0] = props;
42 - $[1] = x;
41 + $[0] = props.bar;
42 + $[1] = props.cond;
43 + $[2] = props.foo;
44 + $[3] = x;
45 } else {
44 - x = $[1];
46 + x = $[3];
47 }
48 return x;
49 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md
+7 -5
@@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = {
28 ```javascript
29 import { c as _c } from "react/compiler-runtime";
30 function useFoo(props) {
31 - const $ = _c(4);
31 + const $ = _c(6);
32 let x;
33 if ($[0] !== props.bar) {
34 x = [];
@@ -38,12 +38,14 @@ function useFoo(props) {
38 } else {
39 x = $[1];
40 }
41 - if ($[2] !== props) {
41 + if ($[2] !== props.bar || $[3] !== props.cond || $[4] !== props.foo) {
42 props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar));
43 - $[2] = props;
44 - $[3] = x;
43 + $[2] = props.bar;
44 + $[3] = props.cond;
45 + $[4] = props.foo;
46 + $[5] = x;
47 } else {
46 - x = $[3];
48 + x = $[5];
49 }
50 return x;
51 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md
+7 -5
@@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime";
39 import { mutate } from "shared-runtime";
40
41 function useFoo(props) {
42 - const $ = _c(2);
42 + const $ = _c(4);
43 let x;
44 - if ($[0] !== props) {
44 + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
45 x = [];
46 x.push(props.bar);
47 if (props.cond) {
@@ -53,10 +53,12 @@ function useFoo(props) {
53 }
54
55 mutate(x);
56 - $[0] = props;
57 - $[1] = x;
56 + $[0] = props.bar;
57 + $[1] = props.cond;
58 + $[2] = props.foo;
59 + $[3] = x;
60 } else {
59 - x = $[1];
61 + x = $[3];
62 }
63 return x;
64 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md
+7 -5
@@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime";
35 import { mutate } from "shared-runtime";
36
37 function useFoo(props) {
38 - const $ = _c(2);
38 + const $ = _c(4);
39 let x;
40 - if ($[0] !== props) {
40 + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
41 ({ x } = { x: [] });
42 x.push(props.bar);
43 if (props.cond) {
@@ -46,10 +46,12 @@ function useFoo(props) {
46 }
47
48 mutate(x);
49 - $[0] = props;
50 - $[1] = x;
49 + $[0] = props.bar;
50 + $[1] = props.cond;
51 + $[2] = props.foo;
52 + $[3] = x;
53 } else {
52 - x = $[1];
54 + x = $[3];
55 }
56 return x;
57 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md
+7 -5
@@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime";
35 import { mutate } from "shared-runtime";
36
37 function useFoo(props) {
38 - const $ = _c(2);
38 + const $ = _c(4);
39 let x;
40 - if ($[0] !== props) {
40 + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
41 x = [];
42 x.push(props.bar);
43 if (props.cond) {
@@ -46,10 +46,12 @@ function useFoo(props) {
46 }
47
48 mutate(x);
49 - $[0] = props;
50 - $[1] = x;
49 + $[0] = props.bar;
50 + $[1] = props.cond;
51 + $[2] = props.foo;
52 + $[3] = x;
53 } else {
52 - x = $[1];
54 + x = $[3];
55 }
56 return x;
57 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md
+16 -15
@@ -33,10 +33,10 @@ function Component(props) {
33 ```javascript
34 import { c as _c } from "react/compiler-runtime";
35 function Component(props) {
36 - const $ = _c(7);
36 + const $ = _c(8);
37 let t0;
38 let y;
39 - if ($[0] !== props) {
39 + if ($[0] !== props.p0 || $[1] !== props.p2) {
40 const x = [];
41 bb0: switch (props.p0) {
42 case 1: {
@@ -45,11 +45,11 @@ function Component(props) {
45 case true: {
46 x.push(props.p2);
47 let t1;
48 - if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
48 + if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
49 t1 = [];
50 - $[3] = t1;
50 + $[4] = t1;
51 } else {
52 - t1 = $[3];
52 + t1 = $[4];
53 }
54 y = t1;
55 }
@@ -62,23 +62,24 @@ function Component(props) {
62 }
63
64 t0 = <Component data={x} />;
65 - $[0] = props;
66 - $[1] = t0;
67 - $[2] = y;
65 + $[0] = props.p0;
66 + $[1] = props.p2;
67 + $[2] = t0;
68 + $[3] = y;
69 } else {
69 - t0 = $[1];
70 - y = $[2];
70 + t0 = $[2];
71 + y = $[3];
72 }
73 const child = t0;
74 y.push(props.p4);
75 let t1;
75 - if ($[4] !== child || $[5] !== y) {
76 + if ($[5] !== child || $[6] !== y) {
77 t1 = <Component data={y}>{child}</Component>;
77 - $[4] = child;
78 - $[5] = y;
79 - $[6] = t1;
78 + $[5] = child;
79 + $[6] = y;
80 + $[7] = t1;
81 } else {
81 - t1 = $[6];
82 + t1 = $[7];
83 }
84 return t1;
85 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md
+14 -12
@@ -28,10 +28,10 @@ function Component(props) {
28 ```javascript
29 import { c as _c } from "react/compiler-runtime";
30 function Component(props) {
31 - const $ = _c(6);
31 + const $ = _c(8);
32 let t0;
33 let y;
34 - if ($[0] !== props) {
34 + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) {
35 const x = [];
36 switch (props.p0) {
37 case true: {
@@ -44,23 +44,25 @@ function Component(props) {
44 }
45
46 t0 = <Component data={x} />;
47 - $[0] = props;
48 - $[1] = t0;
49 - $[2] = y;
47 + $[0] = props.p0;
48 + $[1] = props.p2;
49 + $[2] = props.p3;
50 + $[3] = t0;
51 + $[4] = y;
52 } else {
51 - t0 = $[1];
52 - y = $[2];
53 + t0 = $[3];
54 + y = $[4];
55 }
56 const child = t0;
57 y.push(props.p4);
58 let t1;
57 - if ($[3] !== child || $[4] !== y) {
59 + if ($[5] !== child || $[6] !== y) {
60 t1 = <Component data={y}>{child}</Component>;
59 - $[3] = child;
60 - $[4] = y;
61 - $[5] = t1;
61 + $[5] = child;
62 + $[6] = y;
63 + $[7] = t1;
64 } else {
63 - t1 = $[5];
65 + t1 = $[7];
66 }
67 return t1;
68 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md
+12 -4
@@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime";
28 const { shallowCopy, throwErrorWithMessage } = require("shared-runtime");
29
30 function Component(props) {
31 - const $ = _c(3);
31 + const $ = _c(5);
32 let x;
33 - if ($[0] !== props.a) {
33 + if ($[0] !== props) {
34 x = [];
35 try {
36 let t0;
@@ -42,9 +42,17 @@ function Component(props) {
42 }
43 x.push(t0);
44 } catch {
45 - x.push(shallowCopy({ a: props.a }));
45 + let t0;
46 + if ($[3] !== props.a) {
47 + t0 = shallowCopy({ a: props.a });
48 + $[3] = props.a;
49 + $[4] = t0;
50 + } else {
51 + t0 = $[4];
52 + }
53 + x.push(t0);
54 }
47 - $[0] = props.a;
55 + $[0] = props;
56 $[1] = x;
57 } else {
58 x = $[1];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md
+2 -2
@@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime";
31 function Component(props) {
32 const $ = _c(4);
33 let t0;
34 - if ($[0] !== props.value) {
34 + if ($[0] !== props) {
35 t0 = () => {
36 try {
37 throwInput([props.value]);
@@ -40,7 +40,7 @@ function Component(props) {
40 return e;
41 }
42 };
43 - $[0] = props.value;
43 + $[0] = props;
44 $[1] = t0;
45 } else {
46 t0 = $[1];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md
+2 -2
@@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime";
33 function Component(props) {
34 const $ = _c(2);
35 let t0;
36 - if ($[0] !== props.value) {
36 + if ($[0] !== props) {
37 const object = {
38 foo() {
39 try {
@@ -46,7 +46,7 @@ function Component(props) {
46 };
47
48 t0 = object.foo();
49 - $[0] = props.value;
49 + $[0] = props;
50 $[1] = t0;
51 } else {
52 t0 = $[1];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md
+15 -7
@@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime";
33 import { useMemo } from "react";
34
35 function Component(props) {
36 - const $ = _c(3);
36 + const $ = _c(6);
37 let t0;
38 bb0: {
39 let y;
40 - if ($[0] !== props) {
40 + if (
41 + $[0] !== props.a ||
42 + $[1] !== props.b ||
43 + $[2] !== props.cond ||
44 + $[3] !== props.cond2
45 + ) {
46 y = [];
47 if (props.cond) {
48 y.push(props.a);
@@ -48,12 +53,15 @@ function Component(props) {
53 }
54
55 y.push(props.b);
51 - $[0] = props;
52 - $[1] = y;
53 - $[2] = t0;
56 + $[0] = props.a;
57 + $[1] = props.b;
58 + $[2] = props.cond;
59 + $[3] = props.cond2;
60 + $[4] = y;
61 + $[5] = t0;
62 } else {
55 - y = $[1];
56 - t0 = $[2];
63 + y = $[4];
64 + t0 = $[5];
65 }
66 t0 = y;
67 }