@@ -21,25 +21,45 @@ export interface NameResolver {
21
21
}
22
22
23
23
export class ExpressionWithWrappedValueInfo {
24
- constructor ( public expression : o . Expression , public needsValueUnwrapper : boolean ) { }
24
+ constructor (
25
+ public expression : o . Expression , public needsValueUnwrapper : boolean ,
26
+ public temporaryCount : number ) { }
25
27
}
26
28
27
29
export function convertCdExpressionToIr (
28
30
nameResolver : NameResolver , implicitReceiver : o . Expression , expression : cdAst . AST ,
29
- valueUnwrapper : o . ReadVarExpr ) : ExpressionWithWrappedValueInfo {
30
- const visitor = new _AstToIrVisitor ( nameResolver , implicitReceiver , valueUnwrapper ) ;
31
+ valueUnwrapper : o . ReadVarExpr , bindingIndex : number ) : ExpressionWithWrappedValueInfo {
32
+ const visitor = new _AstToIrVisitor ( nameResolver , implicitReceiver , valueUnwrapper , bindingIndex ) ;
31
33
const irAst : o . Expression = expression . visit ( visitor , _Mode . Expression ) ;
32
- return new ExpressionWithWrappedValueInfo ( irAst , visitor . needsValueUnwrapper ) ;
34
+ return new ExpressionWithWrappedValueInfo (
35
+ irAst , visitor . needsValueUnwrapper , visitor . temporaryCount ) ;
33
36
}
34
37
35
38
export function convertCdStatementToIr (
36
- nameResolver : NameResolver , implicitReceiver : o . Expression , stmt : cdAst . AST ) : o . Statement [ ] {
37
- const visitor = new _AstToIrVisitor ( nameResolver , implicitReceiver , null ) ;
39
+ nameResolver : NameResolver , implicitReceiver : o . Expression , stmt : cdAst . AST ,
40
+ bindingIndex : number ) : o . Statement [ ] {
41
+ const visitor = new _AstToIrVisitor ( nameResolver , implicitReceiver , null , bindingIndex ) ;
38
42
let statements : o . Statement [ ] = [ ] ;
39
43
flattenStatements ( stmt . visit ( visitor , _Mode . Statement ) , statements ) ;
44
+ prependTemporaryDecls ( visitor . temporaryCount , bindingIndex , statements ) ;
40
45
return statements ;
41
46
}
42
47
48
+ function temporaryName ( bindingIndex : number , temporaryNumber : number ) : string {
49
+ return `tmp_${ bindingIndex } _${ temporaryNumber } ` ;
50
+ }
51
+
52
+ export function temporaryDeclaration ( bindingIndex : number , temporaryNumber : number ) : o . Statement {
53
+ return new o . DeclareVarStmt ( temporaryName ( bindingIndex , temporaryNumber ) , o . NULL_EXPR ) ;
54
+ }
55
+
56
+ function prependTemporaryDecls (
57
+ temporaryCount : number , bindingIndex : number , statements : o . Statement [ ] ) {
58
+ for ( let i = temporaryCount - 1 ; i >= 0 ; i -- ) {
59
+ statements . unshift ( temporaryDeclaration ( bindingIndex , i ) ) ;
60
+ }
61
+ }
62
+
43
63
enum _Mode {
44
64
Statement ,
45
65
Expression
@@ -66,13 +86,15 @@ function convertToStatementIfNeeded(mode: _Mode, expr: o.Expression): o.Expressi
66
86
}
67
87
68
88
class _AstToIrVisitor implements cdAst . AstVisitor {
69
- private _map = new Map < cdAst . AST , cdAst . AST > ( ) ;
70
-
89
+ private _nodeMap = new Map < cdAst . AST , cdAst . AST > ( ) ;
90
+ private _resultMap = new Map < cdAst . AST , o . Expression > ( ) ;
91
+ private _currentTemporary : number = 0 ;
71
92
public needsValueUnwrapper : boolean = false ;
93
+ public temporaryCount : number = 0 ;
72
94
73
95
constructor (
74
96
private _nameResolver : NameResolver , private _implicitReceiver : o . Expression ,
75
- private _valueUnwrapper : o . ReadVarExpr ) { }
97
+ private _valueUnwrapper : o . ReadVarExpr , private bindingIndex : number ) { }
76
98
77
99
visitBinary ( ast : cdAst . Binary , mode : _Mode ) : any {
78
100
var op : o . BinaryOperator ;
@@ -273,7 +295,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
273
295
}
274
296
275
297
private visit ( ast : cdAst . AST , mode : _Mode ) : any {
276
- return ( this . _map . get ( ast ) || ast ) . visit ( this , mode ) ;
298
+ const result = this . _resultMap . get ( ast ) ;
299
+ if ( result ) return result ;
300
+ return ( this . _nodeMap . get ( ast ) || ast ) . visit ( this , mode ) ;
277
301
}
278
302
279
303
private convertSafeAccess (
@@ -317,17 +341,30 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
317
341
// Notice that the first guard condition is the left hand of the left most safe access node
318
342
// which comes in as leftMostSafe to this routine.
319
343
320
- const condition = this . visit ( leftMostSafe . receiver , mode ) . isBlank ( ) ;
344
+ let guardedExpression = this . visit ( leftMostSafe . receiver , mode ) ;
345
+ let temporary : o . ReadVarExpr ;
346
+ if ( this . needsTemporary ( leftMostSafe . receiver ) ) {
347
+ // If the expression has method calls or pipes then we need to save the result into a
348
+ // temporary variable to avoid calling stateful or impure code more than once.
349
+ temporary = this . allocateTemporary ( ) ;
350
+
351
+ // Preserve the result in the temporary variable
352
+ guardedExpression = temporary . set ( guardedExpression ) ;
353
+
354
+ // Ensure all further references to the guarded expression refer to the temporary instead.
355
+ this . _resultMap . set ( leftMostSafe . receiver , temporary ) ;
356
+ }
357
+ const condition = guardedExpression . isBlank ( ) ;
321
358
322
359
// Convert the ast to an unguarded access to the receiver's member. The map will substitute
323
360
// leftMostNode with its unguarded version in the call to `this.visit()`.
324
361
if ( leftMostSafe instanceof cdAst . SafeMethodCall ) {
325
- this . _map . set (
362
+ this . _nodeMap . set (
326
363
leftMostSafe ,
327
364
new cdAst . MethodCall (
328
365
leftMostSafe . span , leftMostSafe . receiver , leftMostSafe . name , leftMostSafe . args ) ) ;
329
366
} else {
330
- this . _map . set (
367
+ this . _nodeMap . set (
331
368
leftMostSafe ,
332
369
new cdAst . PropertyRead ( leftMostSafe . span , leftMostSafe . receiver , leftMostSafe . name ) ) ;
333
370
}
@@ -337,7 +374,12 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
337
374
338
375
// Remove the mapping. This is not strictly required as the converter only traverses each node
339
376
// once but is safer if the conversion is changed to traverse the nodes more than once.
340
- this . _map . delete ( leftMostSafe ) ;
377
+ this . _nodeMap . delete ( leftMostSafe ) ;
378
+
379
+ // If we allcoated a temporary, release it.
380
+ if ( temporary ) {
381
+ this . releaseTemporary ( temporary ) ;
382
+ }
341
383
342
384
// Produce the conditional
343
385
return condition . conditional ( o . literal ( null ) , access ) ;
@@ -351,8 +393,8 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
351
393
// then to:
352
394
// a == null ? null : a.b.c == null ? null : a.b.c.d.e
353
395
private leftMostSafeNode ( ast : cdAst . AST ) : cdAst . SafePropertyRead | cdAst . SafeMethodCall {
354
- let visit = ( visitor : cdAst . AstVisitor , ast : cdAst . AST ) : any => {
355
- return ( this . _map . get ( ast ) || ast ) . visit ( visitor ) ;
396
+ const visit = ( visitor : cdAst . AstVisitor , ast : cdAst . AST ) : any => {
397
+ return ( this . _nodeMap . get ( ast ) || ast ) . visit ( visitor ) ;
356
398
} ;
357
399
return ast . visit ( {
358
400
visitBinary ( ast : cdAst . Binary ) { return null ; } ,
@@ -378,6 +420,55 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
378
420
}
379
421
} ) ;
380
422
}
423
+
424
+ // Returns true of the AST includes a method or a pipe indicating that, if the
425
+ // expression is used as the target of a safe property or method access then
426
+ // the expression should be stored into a temporary variable.
427
+ private needsTemporary ( ast : cdAst . AST ) : boolean {
428
+ const visit = ( visitor : cdAst . AstVisitor , ast : cdAst . AST ) : boolean => {
429
+ return ast && ( this . _nodeMap . get ( ast ) || ast ) . visit ( visitor ) ;
430
+ } ;
431
+ const visitSome = ( visitor : cdAst . AstVisitor , ast : cdAst . AST [ ] ) : boolean => {
432
+ return ast . some ( ast => visit ( visitor , ast ) ) ;
433
+ } ;
434
+ return ast . visit ( {
435
+ visitBinary ( ast : cdAst . Binary ) :
436
+ boolean { return visit ( this , ast . left ) || visit ( this , ast . right ) ; } ,
437
+ visitChain ( ast : cdAst . Chain ) { return false ; } ,
438
+ visitConditional ( ast : cdAst . Conditional ) :
439
+ boolean { return visit ( this , ast . condition ) || visit ( this , ast . trueExp ) ||
440
+ visit ( this , ast . falseExp ) ; } ,
441
+ visitFunctionCall ( ast : cdAst . FunctionCall ) { return true ; } ,
442
+ visitImplicitReceiver ( ast : cdAst . ImplicitReceiver ) { return false ; } ,
443
+ visitInterpolation ( ast : cdAst . Interpolation ) { return visitSome ( this , ast . expressions ) ; } ,
444
+ visitKeyedRead ( ast : cdAst . KeyedRead ) { return false ; } ,
445
+ visitKeyedWrite ( ast : cdAst . KeyedWrite ) { return false ; } ,
446
+ visitLiteralArray ( ast : cdAst . LiteralArray ) { return true ; } ,
447
+ visitLiteralMap ( ast : cdAst . LiteralMap ) { return true ; } ,
448
+ visitLiteralPrimitive ( ast : cdAst . LiteralPrimitive ) { return false ; } ,
449
+ visitMethodCall ( ast : cdAst . MethodCall ) { return true ; } ,
450
+ visitPipe ( ast : cdAst . BindingPipe ) { return true ; } ,
451
+ visitPrefixNot ( ast : cdAst . PrefixNot ) { return visit ( this , ast . expression ) ; } ,
452
+ visitPropertyRead ( ast : cdAst . PropertyRead ) { return false ; } ,
453
+ visitPropertyWrite ( ast : cdAst . PropertyWrite ) { return false ; } ,
454
+ visitQuote ( ast : cdAst . Quote ) { return false ; } ,
455
+ visitSafeMethodCall ( ast : cdAst . SafeMethodCall ) { return true ; } ,
456
+ visitSafePropertyRead ( ast : cdAst . SafePropertyRead ) { return false ; }
457
+ } ) ;
458
+ }
459
+
460
+ private allocateTemporary ( ) : o . ReadVarExpr {
461
+ const tempNumber = this . _currentTemporary ++ ;
462
+ this . temporaryCount = Math . max ( this . _currentTemporary , this . temporaryCount ) ;
463
+ return new o . ReadVarExpr ( temporaryName ( this . bindingIndex , tempNumber ) ) ;
464
+ }
465
+
466
+ private releaseTemporary ( temporary : o . ReadVarExpr ) {
467
+ this . _currentTemporary -- ;
468
+ if ( temporary . name != temporaryName ( this . bindingIndex , this . _currentTemporary ) ) {
469
+ throw new BaseException ( `Temporary ${ temporary . name } released out of order` ) ;
470
+ }
471
+ }
381
472
}
382
473
383
474
function flattenStatements ( arg : any , output : o . Statement [ ] ) {
0 commit comments