@@ -60,13 +60,14 @@ export class ReactiveScopeDependencyTree {
60
60
const { path} = dep ;
61
61
let currNode = this . #getOrCreateRoot( dep . identifier ) ;
62
62
63
- const accessType = inConditional
64
- ? PropertyAccessType . ConditionalAccess
65
- : PropertyAccessType . UnconditionalAccess ;
66
-
67
63
for ( const item of path ) {
68
64
// all properties read 'on the way' to a dependency are marked as 'access'
69
65
let currChild = getOrMakeProperty ( currNode , item . property ) ;
66
+ const accessType = inConditional
67
+ ? PropertyAccessType . ConditionalAccess
68
+ : item . optional
69
+ ? PropertyAccessType . OptionalAccess
70
+ : PropertyAccessType . UnconditionalAccess ;
70
71
currChild . accessType = merge ( currChild . accessType , accessType ) ;
71
72
currNode = currChild ;
72
73
}
@@ -77,18 +78,22 @@ export class ReactiveScopeDependencyTree {
77
78
*/
78
79
const depType = inConditional
79
80
? PropertyAccessType . ConditionalDependency
80
- : PropertyAccessType . UnconditionalDependency ;
81
+ : isOptional ( currNode . accessType )
82
+ ? PropertyAccessType . OptionalDependency
83
+ : PropertyAccessType . UnconditionalDependency ;
81
84
82
85
currNode . accessType = merge ( currNode . accessType , depType ) ;
83
86
}
84
87
85
88
deriveMinimalDependencies ( ) : Set < ReactiveScopeDependency > {
86
89
const results = new Set < ReactiveScopeDependency > ( ) ;
87
90
for ( const [ rootId , rootNode ] of this . #roots. entries ( ) ) {
88
- const deps = deriveMinimalDependenciesInSubtree ( rootNode ) ;
91
+ const deps = deriveMinimalDependenciesInSubtree ( rootNode , null ) ;
89
92
CompilerError . invariant (
90
93
deps . every (
91
- dep => dep . accessType === PropertyAccessType . UnconditionalDependency ,
94
+ dep =>
95
+ dep . accessType === PropertyAccessType . UnconditionalDependency ||
96
+ dep . accessType == PropertyAccessType . OptionalDependency ,
92
97
) ,
93
98
{
94
99
reason :
@@ -173,6 +178,27 @@ export class ReactiveScopeDependencyTree {
173
178
}
174
179
return res . flat ( ) . join ( '\n' ) ;
175
180
}
181
+
182
+ debug ( ) : string {
183
+ const buf : Array < string > = [ `tree() [` ] ;
184
+ for ( const [ rootId , rootNode ] of this . #roots) {
185
+ buf . push ( `${ printIdentifier ( rootId ) } (${ rootNode . accessType } ):` ) ;
186
+ this . #debugImpl( buf , rootNode , 1 ) ;
187
+ }
188
+ buf . push ( ']' ) ;
189
+ return buf . length > 2 ? buf . join ( '\n' ) : buf . join ( '' ) ;
190
+ }
191
+
192
+ #debugImpl(
193
+ buf : Array < string > ,
194
+ node : DependencyNode ,
195
+ depth : number = 0 ,
196
+ ) : void {
197
+ for ( const [ property , childNode ] of node . properties ) {
198
+ buf . push ( `${ ' ' . repeat ( depth ) } .${ property } (${ childNode . accessType } ):` ) ;
199
+ this . #debugImpl( buf , childNode , depth + 1 ) ;
200
+ }
201
+ }
176
202
}
177
203
178
204
/*
@@ -196,8 +222,10 @@ export class ReactiveScopeDependencyTree {
196
222
*/
197
223
enum PropertyAccessType {
198
224
ConditionalAccess = 'ConditionalAccess' ,
225
+ OptionalAccess = 'OptionalAccess' ,
199
226
UnconditionalAccess = 'UnconditionalAccess' ,
200
227
ConditionalDependency = 'ConditionalDependency' ,
228
+ OptionalDependency = 'OptionalDependency' ,
201
229
UnconditionalDependency = 'UnconditionalDependency' ,
202
230
}
203
231
@@ -211,9 +239,16 @@ function isUnconditional(access: PropertyAccessType): boolean {
211
239
function isDependency ( access : PropertyAccessType ) : boolean {
212
240
return (
213
241
access === PropertyAccessType . ConditionalDependency ||
242
+ access === PropertyAccessType . OptionalDependency ||
214
243
access === PropertyAccessType . UnconditionalDependency
215
244
) ;
216
245
}
246
+ function isOptional ( access : PropertyAccessType ) : boolean {
247
+ return (
248
+ access === PropertyAccessType . OptionalAccess ||
249
+ access === PropertyAccessType . OptionalDependency
250
+ ) ;
251
+ }
217
252
218
253
function merge (
219
254
access1 : PropertyAccessType ,
@@ -222,6 +257,7 @@ function merge(
222
257
const resultIsUnconditional =
223
258
isUnconditional ( access1 ) || isUnconditional ( access2 ) ;
224
259
const resultIsDependency = isDependency ( access1 ) || isDependency ( access2 ) ;
260
+ const resultIsOptional = isOptional ( access1 ) || isOptional ( access2 ) ;
225
261
226
262
/*
227
263
* Straightforward merge.
@@ -237,6 +273,12 @@ function merge(
237
273
} else {
238
274
return PropertyAccessType . UnconditionalAccess ;
239
275
}
276
+ } else if ( resultIsOptional ) {
277
+ if ( resultIsDependency ) {
278
+ return PropertyAccessType . OptionalDependency ;
279
+ } else {
280
+ return PropertyAccessType . OptionalAccess ;
281
+ }
240
282
} else {
241
283
if ( resultIsDependency ) {
242
284
return PropertyAccessType . ConditionalDependency ;
@@ -256,19 +298,34 @@ type ReduceResultNode = {
256
298
accessType : PropertyAccessType ;
257
299
} ;
258
300
259
- const promoteUncondResult = [
260
- {
301
+ function promoteResult (
302
+ accessType : PropertyAccessType ,
303
+ path : { property : string ; optional : boolean } | null ,
304
+ ) : Array < ReduceResultNode > {
305
+ const result : ReduceResultNode = {
261
306
relativePath : [ ] ,
262
- accessType : PropertyAccessType . UnconditionalDependency ,
263
- } ,
264
- ] ;
307
+ accessType,
308
+ } ;
309
+ if ( path !== null ) {
310
+ result . relativePath . push ( path ) ;
311
+ }
312
+ return [ result ] ;
313
+ }
265
314
266
- const promoteCondResult = [
267
- {
268
- relativePath : [ ] ,
269
- accessType : PropertyAccessType . ConditionalDependency ,
270
- } ,
271
- ] ;
315
+ function prependPath (
316
+ results : Array < ReduceResultNode > ,
317
+ path : { property : string ; optional : boolean } | null ,
318
+ ) : Array < ReduceResultNode > {
319
+ if ( path === null ) {
320
+ return results ;
321
+ }
322
+ return results . map ( result => {
323
+ return {
324
+ accessType : result . accessType ,
325
+ relativePath : [ path , ...result . relativePath ] ,
326
+ } ;
327
+ } ) ;
328
+ }
272
329
273
330
/*
274
331
* Recursively calculates minimal dependencies in a subtree.
@@ -277,42 +334,76 @@ const promoteCondResult = [
277
334
*/
278
335
function deriveMinimalDependenciesInSubtree (
279
336
dep : DependencyNode ,
337
+ property : string | null ,
280
338
) : Array < ReduceResultNode > {
281
339
const results : Array < ReduceResultNode > = [ ] ;
282
340
for ( const [ childName , childNode ] of dep . properties ) {
283
- const childResult = deriveMinimalDependenciesInSubtree ( childNode ) . map (
284
- ( { relativePath, accessType} ) => {
285
- return {
286
- relativePath : [
287
- { property : childName , optional : false } ,
288
- ...relativePath ,
289
- ] ,
290
- accessType,
291
- } ;
292
- } ,
341
+ const childResult = deriveMinimalDependenciesInSubtree (
342
+ childNode ,
343
+ childName ,
293
344
) ;
294
345
results . push ( ...childResult ) ;
295
346
}
296
347
297
348
switch ( dep . accessType ) {
298
349
case PropertyAccessType . UnconditionalDependency : {
299
- return promoteUncondResult ;
350
+ return promoteResult (
351
+ PropertyAccessType . UnconditionalDependency ,
352
+ property !== null ? { property, optional : false } : null ,
353
+ ) ;
300
354
}
301
355
case PropertyAccessType . UnconditionalAccess : {
302
356
if (
303
357
results . every (
304
358
( { accessType} ) =>
305
- accessType === PropertyAccessType . UnconditionalDependency ,
359
+ accessType === PropertyAccessType . UnconditionalDependency ||
360
+ accessType === PropertyAccessType . OptionalDependency ,
306
361
)
307
362
) {
308
363
// all children are unconditional dependencies, return them to preserve granularity
309
- return results ;
364
+ return prependPath (
365
+ results ,
366
+ property !== null ? { property, optional : false } : null ,
367
+ ) ;
310
368
} else {
311
369
/*
312
370
* at least one child is accessed conditionally, so this node needs to be promoted to
313
371
* unconditional dependency
314
372
*/
315
- return promoteUncondResult ;
373
+ return promoteResult (
374
+ PropertyAccessType . UnconditionalDependency ,
375
+ property !== null ? { property, optional : false } : null ,
376
+ ) ;
377
+ }
378
+ }
379
+ case PropertyAccessType . OptionalDependency : {
380
+ return promoteResult (
381
+ PropertyAccessType . OptionalDependency ,
382
+ property !== null ? { property, optional : true } : null ,
383
+ ) ;
384
+ }
385
+ case PropertyAccessType . OptionalAccess : {
386
+ if (
387
+ results . every (
388
+ ( { accessType} ) =>
389
+ accessType === PropertyAccessType . UnconditionalDependency ||
390
+ accessType === PropertyAccessType . OptionalDependency ,
391
+ )
392
+ ) {
393
+ // all children are unconditional dependencies, return them to preserve granularity
394
+ return prependPath (
395
+ results ,
396
+ property !== null ? { property, optional : true } : null ,
397
+ ) ;
398
+ } else {
399
+ /*
400
+ * at least one child is accessed conditionally, so this node needs to be promoted to
401
+ * unconditional dependency
402
+ */
403
+ return promoteResult (
404
+ PropertyAccessType . OptionalDependency ,
405
+ property !== null ? { property, optional : true } : null ,
406
+ ) ;
316
407
}
317
408
}
318
409
case PropertyAccessType . ConditionalAccess :
@@ -328,13 +419,19 @@ function deriveMinimalDependenciesInSubtree(
328
419
* unconditional access.
329
420
* Truncate results of child nodes here, since we shouldn't access them anyways
330
421
*/
331
- return promoteCondResult ;
422
+ return promoteResult (
423
+ PropertyAccessType . ConditionalDependency ,
424
+ property !== null ? { property, optional : true } : null ,
425
+ ) ;
332
426
} else {
333
427
/*
334
428
* at least one child is accessed unconditionally, so this node can be promoted to
335
429
* unconditional dependency
336
430
*/
337
- return promoteUncondResult ;
431
+ return promoteResult (
432
+ PropertyAccessType . UnconditionalDependency ,
433
+ property !== null ? { property, optional : true } : null ,
434
+ ) ;
338
435
}
339
436
}
340
437
default : {
0 commit comments