55 * LICENSE file in the root directory of this source tree.
66 */
77
8+ import { PATTERNLIKE_TYPES } from '@babel/types' ;
89import { CompilerError } from '../CompilerError' ;
910import { Identifier , ReactiveScopeDependency } from '../HIR' ;
1011import { printIdentifier } from '../HIR/PrintHIR' ;
1112import { 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 ;
5457export 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 */
239278enum 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 {
253294function 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
296357type 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
0 commit comments