Skip to content

Commit 298e2dd

Browse files
committed
[wip] Early exploration of optional deps
ghstack-source-id: 1dc9c94 Pull Request resolved: #30801
1 parent a82fe37 commit 298e2dd

File tree

6 files changed

+246
-19
lines changed

6 files changed

+246
-19
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1494,7 +1494,7 @@ export type ReactiveScopeDeclaration = {
14941494

14951495
export type ReactiveScopeDependency = {
14961496
identifier: Identifier;
1497-
path: Array<string>;
1497+
path: Array<{property: string; optional: boolean}>;
14981498
};
14991499

15001500
/*

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

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import {PATTERNLIKE_TYPES} from '@babel/types';
89
import {CompilerError} from '../CompilerError';
910
import {Identifier, ReactiveScopeDependency} from '../HIR';
1011
import {printIdentifier} from '../HIR/PrintHIR';
1112
import {assertExhaustive} from '../Utils/utils';
13+
import {printReactiveScopeSummary} from './PrintReactiveFunction';
1214

1315
/*
1416
* We need to understand optional member expressions only when determining
@@ -51,7 +53,9 @@ export type ReactiveScopePropertyDependency = ReactiveScopeDependency & {
5153
* @param initialDeps
5254
* @returns
5355
*/
56+
let nextId = 0;
5457
export class ReactiveScopeDependencyTree {
58+
#inst: number = nextId++;
5559
#roots: Map<Identifier, DependencyNode> = new Map();
5660

5761
#getOrCreateRoot(identifier: Identifier): DependencyNode {
@@ -69,14 +73,19 @@ export class ReactiveScopeDependencyTree {
6973
}
7074

7175
add(dep: ReactiveScopePropertyDependency, inConditional: boolean): void {
76+
// console.log(
77+
// `add(${this.#inst}): ${printIdentifier(dep.identifier)}${dep.path.length ? `.${dep.path.join('.')}` : ''}${dep.optionalPath.length ? `?.${dep.optionalPath.join('?.')}` : ''} inConditional=${inConditional}`,
78+
// );
79+
// console.log(this.debug());
80+
// console.log();
7281
const {path, optionalPath} = dep;
7382
let currNode = this.#getOrCreateRoot(dep.identifier);
7483

7584
const accessType = inConditional
7685
? PropertyAccessType.ConditionalAccess
7786
: PropertyAccessType.UnconditionalAccess;
7887

79-
for (const property of path) {
88+
for (const {property} of path) {
8089
// all properties read 'on the way' to a dependency are marked as 'access'
8190
let currChild = getOrMakeProperty(currNode, property);
8291
currChild.accessType = merge(currChild.accessType, accessType);
@@ -111,26 +120,35 @@ export class ReactiveScopeDependencyTree {
111120
let currChild = getOrMakeProperty(currNode, property);
112121
currChild.accessType = merge(
113122
currChild.accessType,
114-
PropertyAccessType.ConditionalAccess,
123+
// Conditional access takes precedence over optional access
124+
inConditional
125+
? PropertyAccessType.ConditionalAccess
126+
: PropertyAccessType.OptionalAccess,
115127
);
116128
currNode = currChild;
117129
}
118130

119131
// The final node should be marked as a conditional dependency.
120132
currNode.accessType = merge(
121133
currNode.accessType,
122-
PropertyAccessType.ConditionalDependency,
134+
// Conditional access takes precedence over optional access
135+
inConditional
136+
? PropertyAccessType.ConditionalDependency
137+
: PropertyAccessType.OptionalDependency,
123138
);
124139
}
125140
}
126141

127142
deriveMinimalDependencies(): Set<ReactiveScopeDependency> {
143+
console.log(this.debug());
128144
const results = new Set<ReactiveScopeDependency>();
129145
for (const [rootId, rootNode] of this.#roots.entries()) {
130146
const deps = deriveMinimalDependenciesInSubtree(rootNode);
131147
CompilerError.invariant(
132148
deps.every(
133-
dep => dep.accessType === PropertyAccessType.UnconditionalDependency,
149+
dep =>
150+
dep.accessType === PropertyAccessType.UnconditionalDependency ||
151+
dep.accessType === PropertyAccessType.OptionalDependency,
134152
),
135153
{
136154
reason:
@@ -215,6 +233,27 @@ export class ReactiveScopeDependencyTree {
215233
}
216234
return res.flat().join('\n');
217235
}
236+
237+
debug(): string {
238+
const buf: Array<string> = [`tree(${this.#inst}) [`];
239+
for (const [rootId, rootNode] of this.#roots) {
240+
buf.push(`${printIdentifier(rootId)} (${rootNode.accessType}):`);
241+
this.#debugImpl(buf, rootNode, 1);
242+
}
243+
buf.push(']');
244+
return buf.length > 2 ? buf.join('\n') : buf.join('');
245+
}
246+
247+
#debugImpl(
248+
buf: Array<string>,
249+
node: DependencyNode,
250+
depth: number = 0,
251+
): void {
252+
for (const [property, childNode] of node.properties) {
253+
buf.push(`${' '.repeat(depth)}.${property} (${childNode.accessType}):`);
254+
this.#debugImpl(buf, childNode, depth + 1);
255+
}
256+
}
218257
}
219258

220259
/*
@@ -237,6 +276,8 @@ export class ReactiveScopeDependencyTree {
237276
* ```
238277
*/
239278
enum PropertyAccessType {
279+
OptionalAccess = 'OptionalAccess',
280+
OptionalDependency = 'OptionalDependency',
240281
ConditionalAccess = 'ConditionalAccess',
241282
UnconditionalAccess = 'UnconditionalAccess',
242283
ConditionalDependency = 'ConditionalDependency',
@@ -253,7 +294,20 @@ function isUnconditional(access: PropertyAccessType): boolean {
253294
function isDependency(access: PropertyAccessType): boolean {
254295
return (
255296
access === PropertyAccessType.ConditionalDependency ||
256-
access === PropertyAccessType.UnconditionalDependency
297+
access === PropertyAccessType.UnconditionalDependency ||
298+
access === PropertyAccessType.OptionalDependency
299+
);
300+
}
301+
function isOptional(access: PropertyAccessType): boolean {
302+
return (
303+
access === PropertyAccessType.OptionalAccess ||
304+
access == PropertyAccessType.OptionalDependency
305+
);
306+
}
307+
function _isConditional(access: PropertyAccessType): boolean {
308+
return (
309+
access === PropertyAccessType.ConditionalAccess ||
310+
access === PropertyAccessType.ConditionalDependency
257311
);
258312
}
259313

@@ -264,6 +318,7 @@ function merge(
264318
const resultIsUnconditional =
265319
isUnconditional(access1) || isUnconditional(access2);
266320
const resultIsDependency = isDependency(access1) || isDependency(access2);
321+
const resultIsOptional = isOptional(access1) || isOptional(access2);
267322

268323
/*
269324
* Straightforward merge.
@@ -279,6 +334,12 @@ function merge(
279334
} else {
280335
return PropertyAccessType.UnconditionalAccess;
281336
}
337+
} else if (resultIsOptional) {
338+
if (resultIsDependency) {
339+
return PropertyAccessType.OptionalDependency;
340+
} else {
341+
return PropertyAccessType.OptionalAccess;
342+
}
282343
} else {
283344
if (resultIsDependency) {
284345
return PropertyAccessType.ConditionalDependency;
@@ -294,24 +355,31 @@ type DependencyNode = {
294355
};
295356

296357
type ReduceResultNode = {
297-
relativePath: Array<string>;
358+
relativePath: Array<{property: string; optional: boolean}>;
298359
accessType: PropertyAccessType;
299360
};
300361

301-
const promoteUncondResult = [
362+
const promoteUncondResult: Array<ReduceResultNode> = [
302363
{
303364
relativePath: [],
304365
accessType: PropertyAccessType.UnconditionalDependency,
305366
},
306367
];
307368

308-
const promoteCondResult = [
369+
const promoteCondResult: Array<ReduceResultNode> = [
309370
{
310371
relativePath: [],
311372
accessType: PropertyAccessType.ConditionalDependency,
312373
},
313374
];
314375

376+
const promoteOptionalResult: Array<ReduceResultNode> = [
377+
{
378+
relativePath: [],
379+
accessType: PropertyAccessType.OptionalDependency,
380+
},
381+
];
382+
315383
/*
316384
* Recursively calculates minimal dependencies in a subtree.
317385
* @param dep DependencyNode representing a dependency subtree.
@@ -337,11 +405,33 @@ function deriveMinimalDependenciesInSubtree(
337405
case PropertyAccessType.UnconditionalDependency: {
338406
return promoteUncondResult;
339407
}
408+
case PropertyAccessType.OptionalDependency: {
409+
if (results.length === 0) {
410+
return promoteOptionalResult;
411+
} else if (
412+
results.every(
413+
({accessType}) =>
414+
accessType === PropertyAccessType.UnconditionalDependency ||
415+
accessType === PropertyAccessType.OptionalDependency,
416+
)
417+
) {
418+
// all children are unconditional dependencies, return them to preserve granularity
419+
return results;
420+
} else {
421+
/*
422+
* at least one child is accessed conditionally, so this node needs to be promoted to
423+
* unconditional dependency
424+
*/
425+
return promoteUncondResult;
426+
}
427+
}
428+
case PropertyAccessType.OptionalAccess:
340429
case PropertyAccessType.UnconditionalAccess: {
341430
if (
342431
results.every(
343432
({accessType}) =>
344-
accessType === PropertyAccessType.UnconditionalDependency,
433+
accessType === PropertyAccessType.UnconditionalDependency ||
434+
accessType === PropertyAccessType.OptionalDependency,
345435
)
346436
) {
347437
// all children are unconditional dependencies, return them to preserve granularity

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,17 @@ function areEqualDependencies(
525525
return true;
526526
}
527527

528-
export function areEqualPaths(a: Array<string>, b: Array<string>): boolean {
529-
return a.length === b.length && a.every((item, ix) => item === b[ix]);
528+
export function areEqualPaths(
529+
a: Array<{property: string; optional: boolean}>,
530+
b: Array<{property: string; optional: boolean}>,
531+
): boolean {
532+
return (
533+
a.length === b.length &&
534+
a.every(
535+
(item, ix) =>
536+
item.property === b[ix].property && item.optional === b[ix].optional,
537+
)
538+
);
530539
}
531540

532541
/**

0 commit comments

Comments
 (0)