Skip to content

Commit e0a4e1e

Browse files
committed
[wip] PropagateScopeDeps understands optionals
ghstack-source-id: 50d3523 Pull Request resolved: #30819
1 parent 7666342 commit e0a4e1e

File tree

5 files changed

+160
-45
lines changed

5 files changed

+160
-45
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
191191
case 'branch': {
192192
value = `[${terminal.id}] Branch (${printPlace(terminal.test)}) then:bb${
193193
terminal.consequent
194-
} else:bb${terminal.alternate}`;
194+
} else:bb${terminal.alternate} fallthrough:bb${terminal.fallthrough}`;
195195
break;
196196
}
197197
case 'logical': {

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {CompilerError} from '../CompilerError';
99
import {DependencyPath, Identifier, ReactiveScopeDependency} from '../HIR';
1010
import {printIdentifier} from '../HIR/PrintHIR';
1111
import {assertExhaustive} from '../Utils/utils';
12+
import {printDependency} from './PrintReactiveFunction';
1213

1314
/*
1415
* We need to understand optional member expressions only when determining
@@ -173,6 +174,27 @@ export class ReactiveScopeDependencyTree {
173174
}
174175
return res.flat().join('\n');
175176
}
177+
178+
debug(): string {
179+
const buf: Array<string> = [`tree() [`];
180+
for (const [rootId, rootNode] of this.#roots) {
181+
buf.push(`${printIdentifier(rootId)} (${rootNode.accessType}):`);
182+
this.#debugImpl(buf, rootNode, 1);
183+
}
184+
buf.push(']');
185+
return buf.length > 2 ? buf.join('\n') : buf.join('');
186+
}
187+
188+
#debugImpl(
189+
buf: Array<string>,
190+
node: DependencyNode,
191+
depth: number = 0,
192+
): void {
193+
for (const [property, childNode] of node.properties) {
194+
buf.push(`${' '.repeat(depth)}.${property} (${childNode.accessType}):`);
195+
this.#debugImpl(buf, childNode, depth + 1);
196+
}
197+
}
176198
}
177199

178200
/*

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export function printDependency(dependency: ReactiveScopeDependency): string {
113113
const identifier =
114114
printIdentifier(dependency.identifier) +
115115
printType(dependency.identifier.type);
116-
return `${identifier}${dependency.path.map(token => `.${token.property}`).join('')}`;
116+
return `${identifier}${dependency.path.map(token => `${token.optional ? '?.' : '.'}${token.property}`).join('')}`;
117117
}
118118

119119
export function printReactiveInstructions(

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts

Lines changed: 134 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import prettyFormat from 'pretty-format';
89
import {CompilerError} from '../CompilerError';
910
import {
1011
areEqualPaths,
@@ -17,25 +18,29 @@ import {
1718
isObjectMethodType,
1819
isRefValueType,
1920
isUseRefType,
21+
LoadLocal,
2022
makeInstructionId,
2123
Place,
2224
PrunedReactiveScopeBlock,
2325
ReactiveFunction,
2426
ReactiveInstruction,
27+
ReactiveOptionalCallValue,
2528
ReactiveScope,
2629
ReactiveScopeBlock,
2730
ReactiveScopeDependency,
2831
ReactiveTerminalStatement,
2932
ReactiveValue,
3033
ScopeId,
3134
} from '../HIR/HIR';
35+
import {printIdentifier, printPlace} from '../HIR/PrintHIR';
3236
import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors';
3337
import {empty, Stack} from '../Utils/Stack';
3438
import {assertExhaustive, Iterable_some} from '../Utils/utils';
3539
import {
3640
ReactiveScopeDependencyTree,
3741
ReactiveScopePropertyDependency,
3842
} from './DeriveMinimalDependencies';
43+
import {printDependency, printReactiveFunction} from './PrintReactiveFunction';
3944
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
4045

4146
/*
@@ -302,6 +307,7 @@ class Context {
302307
#properties: Map<Identifier, ReactiveScopePropertyDependency> = new Map();
303308
#temporaries: Map<Identifier, Place> = new Map();
304309
#inConditionalWithinScope: boolean = false;
310+
inOptional: boolean = false;
305311
/*
306312
* Reactive dependencies used unconditionally in the current conditional.
307313
* Composed of dependencies:
@@ -465,6 +471,7 @@ class Context {
465471
#getProperty(
466472
object: Place,
467473
property: string,
474+
optional: boolean,
468475
): ReactiveScopePropertyDependency {
469476
const resolvedObject = this.resolveTemporary(object);
470477
const resolvedDependency = this.#properties.get(resolvedObject.identifier);
@@ -485,13 +492,18 @@ class Context {
485492
};
486493
}
487494

488-
objectDependency.path.push({property, optional: false});
495+
objectDependency.path.push({property, optional});
489496

490497
return objectDependency;
491498
}
492499

493-
declareProperty(lvalue: Place, object: Place, property: string): void {
494-
const nextDependency = this.#getProperty(object, property);
500+
declareProperty(
501+
lvalue: Place,
502+
object: Place,
503+
property: string,
504+
optional: boolean,
505+
): void {
506+
const nextDependency = this.#getProperty(object, property, optional);
495507
this.#properties.set(lvalue.identifier, nextDependency);
496508
}
497509

@@ -571,8 +583,8 @@ class Context {
571583
this.visitDependency(dependency);
572584
}
573585

574-
visitProperty(object: Place, property: string): void {
575-
const nextDependency = this.#getProperty(object, property);
586+
visitProperty(object: Place, property: string, optional: boolean): void {
587+
const nextDependency = this.#getProperty(object, property, optional);
576588
this.visitDependency(nextDependency);
577589
}
578590

@@ -744,51 +756,102 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
744756
});
745757
}
746758

759+
visitOptionalExpression(
760+
context: Context,
761+
id: InstructionId,
762+
value: ReactiveOptionalCallValue,
763+
lvalue: Place | null,
764+
): void {
765+
const wasWithinOptional = context.inOptional;
766+
const isOptional = wasWithinOptional || value.optional;
767+
const inner = value.value;
768+
/*
769+
* OptionalExpression value is a SequenceExpression where the instructions
770+
* represent the code prior to the `?` and the final value represents the
771+
* conditional code that follows.
772+
*/
773+
CompilerError.invariant(inner.kind === 'SequenceExpression', {
774+
reason: 'Expected OptionalExpression value to be a SequenceExpression',
775+
description: `Found a \`${value.kind}\``,
776+
loc: value.loc,
777+
});
778+
// Instructions are the unconditionally executed portion before the `?`
779+
context.inOptional = isOptional;
780+
for (const instr of inner.instructions) {
781+
this.visitInstruction(instr, context);
782+
}
783+
context.inOptional = wasWithinOptional;
784+
const innerValue = inner.value;
785+
if (
786+
innerValue.kind !== 'SequenceExpression' ||
787+
innerValue.value.kind !== 'LoadLocal'
788+
) {
789+
CompilerError.invariant(false, {
790+
reason:
791+
'Expected OptionalExpression inner value to be a SequenceExpression',
792+
description: `Found a \`${innerValue.kind}\``,
793+
loc: innerValue.loc,
794+
});
795+
}
796+
if (
797+
isOptional &&
798+
innerValue.instructions.length === 1 &&
799+
innerValue.instructions[0].value.kind === 'PropertyLoad' &&
800+
innerValue.instructions[0].lvalue !== null &&
801+
innerValue.instructions[0].lvalue.identifier.id ===
802+
innerValue.value.place.identifier.id
803+
) {
804+
const propertyLoad = innerValue.instructions[0].value;
805+
if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) {
806+
context.declareProperty(
807+
lvalue,
808+
propertyLoad.object,
809+
propertyLoad.property,
810+
isOptional,
811+
);
812+
} else {
813+
context.visitProperty(
814+
propertyLoad.object,
815+
propertyLoad.property,
816+
isOptional,
817+
);
818+
}
819+
if (isOptional && !wasWithinOptional && lvalue !== null) {
820+
context.visitOperand(lvalue);
821+
}
822+
return;
823+
}
824+
context.enterConditional(() => {
825+
this.visitReactiveValue(context, id, innerValue, lvalue);
826+
});
827+
}
828+
747829
visitReactiveValue(
748830
context: Context,
749831
id: InstructionId,
750832
value: ReactiveValue,
833+
lvalue: Place | null,
751834
): void {
752835
switch (value.kind) {
753836
case 'OptionalExpression': {
754-
const inner = value.value;
755-
/*
756-
* OptionalExpression value is a SequenceExpression where the instructions
757-
* represent the code prior to the `?` and the final value represents the
758-
* conditional code that follows.
759-
*/
760-
CompilerError.invariant(inner.kind === 'SequenceExpression', {
761-
reason:
762-
'Expected OptionalExpression value to be a SequenceExpression',
763-
description: `Found a \`${value.kind}\``,
764-
loc: value.loc,
765-
suggestions: null,
766-
});
767-
// Instructions are the unconditionally executed portion before the `?`
768-
for (const instr of inner.instructions) {
769-
this.visitInstruction(instr, context);
770-
}
771-
// The final value is the conditional portion following the `?`
772-
context.enterConditional(() => {
773-
this.visitReactiveValue(context, id, inner.value);
774-
});
837+
this.visitOptionalExpression(context, id, value, lvalue);
775838
break;
776839
}
777840
case 'LogicalExpression': {
778-
this.visitReactiveValue(context, id, value.left);
841+
this.visitReactiveValue(context, id, value.left, null);
779842
context.enterConditional(() => {
780-
this.visitReactiveValue(context, id, value.right);
843+
this.visitReactiveValue(context, id, value.right, lvalue);
781844
});
782845
break;
783846
}
784847
case 'ConditionalExpression': {
785-
this.visitReactiveValue(context, id, value.test);
848+
this.visitReactiveValue(context, id, value.test, null);
786849

787850
const consequentDeps = context.enterConditional(() => {
788-
this.visitReactiveValue(context, id, value.consequent);
851+
this.visitReactiveValue(context, id, value.consequent, null);
789852
});
790853
const alternateDeps = context.enterConditional(() => {
791-
this.visitReactiveValue(context, id, value.alternate);
854+
this.visitReactiveValue(context, id, value.alternate, null);
792855
});
793856
context.promoteDepsFromExhaustiveConditionals([
794857
consequentDeps,
@@ -797,10 +860,34 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
797860
break;
798861
}
799862
case 'SequenceExpression': {
863+
/**
864+
* Sequence
865+
* <lvalue> = Sequence <-- we're here
866+
* <lvalue> = ...
867+
* LoadLocal <lvalue>
868+
*/
869+
if (
870+
lvalue !== null &&
871+
value.instructions.length === 1 &&
872+
value.value.kind === 'LoadLocal' &&
873+
value.value.place.identifier.id === lvalue.identifier.id &&
874+
value.instructions[0].lvalue !== null &&
875+
value.instructions[0].lvalue.identifier.id ===
876+
value.value.place.identifier.id
877+
) {
878+
this.visitInstructionValue(
879+
context,
880+
value.instructions[0].id,
881+
value.instructions[0].value,
882+
lvalue,
883+
);
884+
break;
885+
}
886+
800887
for (const instr of value.instructions) {
801888
this.visitInstruction(instr, context);
802889
}
803-
this.visitInstructionValue(context, id, value.value, null);
890+
this.visitInstructionValue(context, id, value.value, lvalue);
804891
break;
805892
}
806893
case 'FunctionExpression': {
@@ -851,9 +938,9 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
851938
}
852939
} else if (value.kind === 'PropertyLoad') {
853940
if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) {
854-
context.declareProperty(lvalue, value.object, value.property);
941+
context.declareProperty(lvalue, value.object, value.property, false);
855942
} else {
856-
context.visitProperty(value.object, value.property);
943+
context.visitProperty(value.object, value.property, false);
857944
}
858945
} else if (value.kind === 'StoreLocal') {
859946
context.visitOperand(value.value);
@@ -896,7 +983,7 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
896983
});
897984
}
898985
} else {
899-
this.visitReactiveValue(context, id, value);
986+
this.visitReactiveValue(context, id, value, lvalue);
900987
}
901988
}
902989

@@ -947,25 +1034,30 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
9471034
break;
9481035
}
9491036
case 'for': {
950-
this.visitReactiveValue(context, terminal.id, terminal.init);
951-
this.visitReactiveValue(context, terminal.id, terminal.test);
1037+
this.visitReactiveValue(context, terminal.id, terminal.init, null);
1038+
this.visitReactiveValue(context, terminal.id, terminal.test, null);
9521039
context.enterConditional(() => {
9531040
this.visitBlock(terminal.loop, context);
9541041
if (terminal.update !== null) {
955-
this.visitReactiveValue(context, terminal.id, terminal.update);
1042+
this.visitReactiveValue(
1043+
context,
1044+
terminal.id,
1045+
terminal.update,
1046+
null,
1047+
);
9561048
}
9571049
});
9581050
break;
9591051
}
9601052
case 'for-of': {
961-
this.visitReactiveValue(context, terminal.id, terminal.init);
1053+
this.visitReactiveValue(context, terminal.id, terminal.init, null);
9621054
context.enterConditional(() => {
9631055
this.visitBlock(terminal.loop, context);
9641056
});
9651057
break;
9661058
}
9671059
case 'for-in': {
968-
this.visitReactiveValue(context, terminal.id, terminal.init);
1060+
this.visitReactiveValue(context, terminal.id, terminal.init, null);
9691061
context.enterConditional(() => {
9701062
this.visitBlock(terminal.loop, context);
9711063
});
@@ -974,12 +1066,12 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
9741066
case 'do-while': {
9751067
this.visitBlock(terminal.loop, context);
9761068
context.enterConditional(() => {
977-
this.visitReactiveValue(context, terminal.id, terminal.test);
1069+
this.visitReactiveValue(context, terminal.id, terminal.test, null);
9781070
});
9791071
break;
9801072
}
9811073
case 'while': {
982-
this.visitReactiveValue(context, terminal.id, terminal.test);
1074+
this.visitReactiveValue(context, terminal.id, terminal.test, null);
9831075
context.enterConditional(() => {
9841076
this.visitBlock(terminal.loop, context);
9851077
});
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// @validatePreserveExistingMemoizationGuarantees
22
function Component(props) {
33
const data = useMemo(() => {
4-
return props.items?.edges?.nodes ?? [];
4+
return props?.items.edges?.nodes.map();
55
}, [props.items?.edges?.nodes]);
6+
// const data = props?.item.edges?.nodes.map();
67
return <Foo data={data} />;
78
}

0 commit comments

Comments
 (0)