Skip to content

Commit aa6bb8e

Browse files
dylhunnalxhub
authored andcommitted
refactor(compiler): Fix a bug in which temporaries were being declared in the wrong places (angular#51950)
Previously, we always generated temporary variable declarations at the beginning of each view's update block. This is wrong, for two reasons: 1. Temporaries can be used in the create block 2. When listeners use temporaries, we should declare them inside the listener. Now, we always place temporaries at the beginning of the enclosing OpList, and recursively try to generate them when we find a listener. PR Close angular#51950
1 parent 7068389 commit aa6bb8e

File tree

2 files changed

+69
-44
lines changed

2 files changed

+69
-44
lines changed

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/nullish_coalescing/TEST_CASES.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"cases": [
44
{
55
"description": "should handle nullish coalescing inside interpolations",
6-
"inputFiles": ["nullish_coalescing_interpolation.ts"],
6+
"inputFiles": [
7+
"nullish_coalescing_interpolation.ts"
8+
],
79
"expectations": [
810
{
911
"files": [
@@ -18,7 +20,9 @@
1820
},
1921
{
2022
"description": "should handle nullish coalescing inside property bindings",
21-
"inputFiles": ["nullish_coalescing_property.ts"],
23+
"inputFiles": [
24+
"nullish_coalescing_property.ts"
25+
],
2226
"expectations": [
2327
{
2428
"files": [
@@ -33,7 +37,9 @@
3337
},
3438
{
3539
"description": "should handle nullish coalescing inside host bindings",
36-
"inputFiles": ["nullish_coalescing_host.ts"],
40+
"inputFiles": [
41+
"nullish_coalescing_host.ts"
42+
],
3743
"expectations": [
3844
{
3945
"files": [
@@ -44,8 +50,7 @@
4450
],
4551
"failureMessage": "Incorrect host bindings"
4652
}
47-
],
48-
"skipForTemplatePipeline": true
53+
]
4954
}
5055
]
5156
}

packages/compiler/src/template/pipeline/src/phases/temporary_variables.ts

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import * as o from '../../../../output/output_ast';
1010
import * as ir from '../../ir';
11-
import {CompilationJob, ComponentCompilationJob} from '../compilation';
11+
import type {CompilationJob, CompilationUnit} from '../compilation';
1212

1313
/**
1414
* Find all assignments and usages of temporary variables, which are linked to each other with cross
@@ -21,49 +21,69 @@ import {CompilationJob, ComponentCompilationJob} from '../compilation';
2121
*/
2222
export function phaseTemporaryVariables(cpl: CompilationJob): void {
2323
for (const unit of cpl.units) {
24-
let opCount = 0;
25-
let generatedStatements: Array<ir.StatementOp<ir.UpdateOp>> = [];
26-
for (const op of unit.ops()) {
27-
// Identify the final time each temp var is read.
28-
const finalReads = new Map<ir.XrefId, ir.ReadTemporaryExpr>();
29-
ir.visitExpressionsInOp(op, expr => {
30-
if (expr instanceof ir.ReadTemporaryExpr) {
31-
finalReads.set(expr.xref, expr);
32-
}
33-
});
24+
unit.create.prepend(generateTemporaries(unit.create) as Array<ir.StatementOp<ir.CreateOp>>);
25+
unit.update.prepend(generateTemporaries(unit.update) as Array<ir.StatementOp<ir.UpdateOp>>);
26+
}
27+
}
28+
29+
function generateTemporaries(ops: ir.OpList<ir.CreateOp|ir.UpdateOp>):
30+
Array<ir.StatementOp<ir.CreateOp|ir.UpdateOp>> {
31+
let opCount = 0;
32+
let generatedStatements: Array<ir.StatementOp<ir.UpdateOp>> = [];
3433

35-
// Name the temp vars, accounting for the fact that a name can be reused after it has been
36-
// read for the final time.
37-
let count = 0;
38-
const assigned = new Set<ir.XrefId>();
39-
const released = new Set<ir.XrefId>();
40-
const defs = new Map<ir.XrefId, string>();
41-
ir.visitExpressionsInOp(op, expr => {
42-
if (expr instanceof ir.AssignTemporaryExpr) {
43-
if (!assigned.has(expr.xref)) {
44-
assigned.add(expr.xref);
45-
// TODO: Exactly replicate the naming scheme used by `TemplateDefinitionBuilder`.
46-
// It seems to rely on an expression index instead of an op index.
47-
defs.set(expr.xref, `tmp_${opCount}_${count++}`);
48-
}
49-
assignName(defs, expr);
50-
} else if (expr instanceof ir.ReadTemporaryExpr) {
51-
if (finalReads.get(expr.xref) === expr) {
52-
released.add(expr.xref);
53-
count--;
54-
}
55-
assignName(defs, expr);
34+
// For each op, search for any variables that are assigned or read. For each variable, generate a
35+
// name and produce a `DeclareVarStmt` to the beginning of the block.
36+
for (const op of ops) {
37+
// Identify the final time each temp var is read.
38+
const finalReads = new Map<ir.XrefId, ir.ReadTemporaryExpr>();
39+
ir.visitExpressionsInOp(op, (expr, flag) => {
40+
if (flag & ir.VisitorContextFlag.InChildOperation) {
41+
return;
42+
}
43+
if (expr instanceof ir.ReadTemporaryExpr) {
44+
finalReads.set(expr.xref, expr);
45+
}
46+
});
47+
48+
// Name the temp vars, accounting for the fact that a name can be reused after it has been
49+
// read for the final time.
50+
let count = 0;
51+
const assigned = new Set<ir.XrefId>();
52+
const released = new Set<ir.XrefId>();
53+
const defs = new Map<ir.XrefId, string>();
54+
ir.visitExpressionsInOp(op, (expr, flag) => {
55+
if (flag & ir.VisitorContextFlag.InChildOperation) {
56+
return;
57+
}
58+
if (expr instanceof ir.AssignTemporaryExpr) {
59+
if (!assigned.has(expr.xref)) {
60+
assigned.add(expr.xref);
61+
// TODO: Exactly replicate the naming scheme used by `TemplateDefinitionBuilder`.
62+
// It seems to rely on an expression index instead of an op index.
63+
defs.set(expr.xref, `tmp_${opCount}_${count++}`);
64+
}
65+
assignName(defs, expr);
66+
} else if (expr instanceof ir.ReadTemporaryExpr) {
67+
if (finalReads.get(expr.xref) === expr) {
68+
released.add(expr.xref);
69+
count--;
5670
}
57-
});
71+
assignName(defs, expr);
72+
}
73+
});
5874

59-
// Add declarations for the temp vars.
60-
generatedStatements.push(
61-
...Array.from(new Set(defs.values()))
62-
.map(name => ir.createStatementOp<ir.UpdateOp>(new o.DeclareVarStmt(name))));
63-
opCount++;
75+
// Add declarations for the temp vars.
76+
generatedStatements.push(
77+
...Array.from(new Set(defs.values()))
78+
.map(name => ir.createStatementOp<ir.UpdateOp>(new o.DeclareVarStmt(name))));
79+
opCount++;
80+
81+
if (op.kind === ir.OpKind.Listener) {
82+
op.handlerOps.prepend(generateTemporaries(op.handlerOps) as ir.UpdateOp[]);
6483
}
65-
unit.update.prepend(generatedStatements);
6684
}
85+
86+
return generatedStatements;
6787
}
6888

6989
/**

0 commit comments

Comments
 (0)