Skip to content

Commit

Permalink
refactor(compiler): save/restore view when listeners read references (#…
Browse files Browse the repository at this point in the history
…50834)

Previously, the template pipeline save/restore view logic only added the
save/restore operation in listeners inside embedded views. However, this
operation is also needed if local refs are accessed within a listener body.

This commit updates the logic to detect more accurately whether save/restore
is needed.

PR Close #50834
  • Loading branch information
alxhub committed Jun 30, 2023
1 parent b66a16e commit 57c9399
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ function recursivelyProcessView(view: ViewCompilation, parentScope: Scope|null):
// Extract a `Scope` from this view.
const scope = getScopeForView(view, parentScope);

// Embedded views require an operation to save/restore the view context.
if (view.parent !== null) {
// Start the view creation block with an operation to save the current view context. This may be
// used to restore the view context in any listeners that may be present.
}

for (const op of view.create) {
switch (op.kind) {
case ir.OpKind.Template:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@

import * as o from '../../../../output/output_ast';
import * as ir from '../../ir';
import type {ComponentCompilation} from '../compilation';
import type {ComponentCompilation, ViewCompilation} from '../compilation';

export function phaseSaveRestoreView(cpl: ComponentCompilation): void {
for (const view of cpl.views.values()) {
if (view === cpl.root) {
// Save/restore operations are not necessary for the root view.
continue;
}

view.create.prepend([
ir.createVariableOp<ir.CreateOp>(
view.tpl.allocateXrefId(), {
Expand All @@ -32,25 +27,45 @@ export function phaseSaveRestoreView(cpl: ComponentCompilation): void {
continue;
}

op.handlerOps.prepend([
ir.createVariableOp<ir.UpdateOp>(
view.tpl.allocateXrefId(), {
kind: ir.SemanticVariableKind.Context,
name: null,
view: view.xref,
},
new ir.RestoreViewExpr(view.xref)),
]);

// The "restore view" operation in listeners requires a call to `resetView` to reset the
// context prior to returning from the listener operation. Find any `return` statements in
// the listener body and wrap them in a call to reset the view.
for (const handlerOp of op.handlerOps) {
if (handlerOp.kind === ir.OpKind.Statement &&
handlerOp.statement instanceof o.ReturnStatement) {
handlerOp.statement.value = new ir.ResetViewExpr(handlerOp.statement.value);
// Embedded views always need the save/restore view operation.
let needsRestoreView = view !== cpl.root;

if (!needsRestoreView) {
for (const handlerOp of op.handlerOps) {
ir.visitExpressionsInOp(handlerOp, expr => {
if (expr instanceof ir.ReferenceExpr) {
// Listeners that reference() a local ref need the save/restore view operation.
needsRestoreView = true;
}
});
}
}

if (needsRestoreView) {
addSaveRestoreViewOperationToListener(view, op);
}
}
}
}

function addSaveRestoreViewOperationToListener(view: ViewCompilation, op: ir.ListenerOp) {
op.handlerOps.prepend([
ir.createVariableOp<ir.UpdateOp>(
view.tpl.allocateXrefId(), {
kind: ir.SemanticVariableKind.Context,
name: null,
view: view.xref,
},
new ir.RestoreViewExpr(view.xref)),
]);

// The "restore view" operation in listeners requires a call to `resetView` to reset the
// context prior to returning from the listener operation. Find any `return` statements in
// the listener body and wrap them in a call to reset the view.
for (const handlerOp of op.handlerOps) {
if (handlerOp.kind === ir.OpKind.Statement &&
handlerOp.statement instanceof o.ReturnStatement) {
handlerOp.statement.value = new ir.ResetViewExpr(handlerOp.statement.value);
}
}
}

0 comments on commit 57c9399

Please sign in to comment.