@angular/compiler 16.1.1 → 16.2.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/esm2022/src/compiler_util/expression_converter.mjs +2 -1
  2. package/esm2022/src/constant_pool.mjs +4 -1
  3. package/esm2022/src/output/output_ast.mjs +81 -1
  4. package/esm2022/src/render3/partial/class_metadata.mjs +1 -1
  5. package/esm2022/src/render3/partial/directive.mjs +1 -1
  6. package/esm2022/src/render3/partial/factory.mjs +1 -1
  7. package/esm2022/src/render3/partial/injectable.mjs +1 -1
  8. package/esm2022/src/render3/partial/injector.mjs +1 -1
  9. package/esm2022/src/render3/partial/ng_module.mjs +1 -1
  10. package/esm2022/src/render3/partial/pipe.mjs +1 -1
  11. package/esm2022/src/template/pipeline/ir/src/element.mjs +5 -2
  12. package/esm2022/src/template/pipeline/ir/src/enums.mjs +21 -1
  13. package/esm2022/src/template/pipeline/ir/src/expression.mjs +146 -1
  14. package/esm2022/src/template/pipeline/ir/src/ops/create.mjs +13 -1
  15. package/esm2022/src/template/pipeline/ir/src/ops/update.mjs +18 -3
  16. package/esm2022/src/template/pipeline/src/emit.mjs +19 -13
  17. package/esm2022/src/template/pipeline/src/ingest.mjs +68 -62
  18. package/esm2022/src/template/pipeline/src/phases/attribute_extraction.mjs +74 -0
  19. package/esm2022/src/template/pipeline/src/phases/expand_safe_reads.mjs +86 -0
  20. package/esm2022/src/template/pipeline/src/phases/nullish_coalescing.mjs +26 -0
  21. package/esm2022/src/version.mjs +1 -1
  22. package/fesm2022/compiler.mjs +1700 -1253
  23. package/fesm2022/compiler.mjs.map +1 -1
  24. package/fesm2022/testing.mjs +1 -1
  25. package/index.d.ts +28 -1
  26. package/package.json +2 -2
  27. package/testing/index.d.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Angular v16.1.1
2
+ * @license Angular v16.2.0-next.0
3
3
  * (c) 2010-2022 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
@@ -1222,6 +1222,9 @@ class ReadVarExpr extends Expression {
1222
1222
  visitExpression(visitor, context) {
1223
1223
  return visitor.visitReadVarExpr(this, context);
1224
1224
  }
1225
+ clone() {
1226
+ return new ReadVarExpr(this.name, this.type, this.sourceSpan);
1227
+ }
1225
1228
  set(value) {
1226
1229
  return new WriteVarExpr(this.name, value, null, this.sourceSpan);
1227
1230
  }
@@ -1240,6 +1243,9 @@ class TypeofExpr extends Expression {
1240
1243
  isConstant() {
1241
1244
  return this.expr.isConstant();
1242
1245
  }
1246
+ clone() {
1247
+ return new TypeofExpr(this.expr.clone());
1248
+ }
1243
1249
  }
1244
1250
  class WrappedNodeExpr extends Expression {
1245
1251
  constructor(node, type, sourceSpan) {
@@ -1255,6 +1261,9 @@ class WrappedNodeExpr extends Expression {
1255
1261
  visitExpression(visitor, context) {
1256
1262
  return visitor.visitWrappedNodeExpr(this, context);
1257
1263
  }
1264
+ clone() {
1265
+ return new WrappedNodeExpr(this.node, this.type, this.sourceSpan);
1266
+ }
1258
1267
  }
1259
1268
  class WriteVarExpr extends Expression {
1260
1269
  constructor(name, value, type, sourceSpan) {
@@ -1271,6 +1280,9 @@ class WriteVarExpr extends Expression {
1271
1280
  visitExpression(visitor, context) {
1272
1281
  return visitor.visitWriteVarExpr(this, context);
1273
1282
  }
1283
+ clone() {
1284
+ return new WriteVarExpr(this.name, this.value.clone(), this.type, this.sourceSpan);
1285
+ }
1274
1286
  toDeclStmt(type, modifiers) {
1275
1287
  return new DeclareVarStmt(this.name, this.value, type, modifiers, this.sourceSpan);
1276
1288
  }
@@ -1295,6 +1307,9 @@ class WriteKeyExpr extends Expression {
1295
1307
  visitExpression(visitor, context) {
1296
1308
  return visitor.visitWriteKeyExpr(this, context);
1297
1309
  }
1310
+ clone() {
1311
+ return new WriteKeyExpr(this.receiver.clone(), this.index.clone(), this.value.clone(), this.type, this.sourceSpan);
1312
+ }
1298
1313
  }
1299
1314
  class WritePropExpr extends Expression {
1300
1315
  constructor(receiver, name, value, type, sourceSpan) {
@@ -1313,6 +1328,9 @@ class WritePropExpr extends Expression {
1313
1328
  visitExpression(visitor, context) {
1314
1329
  return visitor.visitWritePropExpr(this, context);
1315
1330
  }
1331
+ clone() {
1332
+ return new WritePropExpr(this.receiver.clone(), this.name, this.value.clone(), this.type, this.sourceSpan);
1333
+ }
1316
1334
  }
1317
1335
  class InvokeFunctionExpr extends Expression {
1318
1336
  constructor(fn, args, type, sourceSpan, pure = false) {
@@ -1331,6 +1349,9 @@ class InvokeFunctionExpr extends Expression {
1331
1349
  visitExpression(visitor, context) {
1332
1350
  return visitor.visitInvokeFunctionExpr(this, context);
1333
1351
  }
1352
+ clone() {
1353
+ return new InvokeFunctionExpr(this.fn.clone(), this.args.map(arg => arg.clone()), this.type, this.sourceSpan, this.pure);
1354
+ }
1334
1355
  }
1335
1356
  class TaggedTemplateExpr extends Expression {
1336
1357
  constructor(tag, template, type, sourceSpan) {
@@ -1349,6 +1370,9 @@ class TaggedTemplateExpr extends Expression {
1349
1370
  visitExpression(visitor, context) {
1350
1371
  return visitor.visitTaggedTemplateExpr(this, context);
1351
1372
  }
1373
+ clone() {
1374
+ return new TaggedTemplateExpr(this.tag.clone(), this.template.clone(), this.type, this.sourceSpan);
1375
+ }
1352
1376
  }
1353
1377
  class InstantiateExpr extends Expression {
1354
1378
  constructor(classExpr, args, type, sourceSpan) {
@@ -1366,6 +1390,9 @@ class InstantiateExpr extends Expression {
1366
1390
  visitExpression(visitor, context) {
1367
1391
  return visitor.visitInstantiateExpr(this, context);
1368
1392
  }
1393
+ clone() {
1394
+ return new InstantiateExpr(this.classExpr.clone(), this.args.map(arg => arg.clone()), this.type, this.sourceSpan);
1395
+ }
1369
1396
  }
1370
1397
  class LiteralExpr extends Expression {
1371
1398
  constructor(value, type, sourceSpan) {
@@ -1381,12 +1408,18 @@ class LiteralExpr extends Expression {
1381
1408
  visitExpression(visitor, context) {
1382
1409
  return visitor.visitLiteralExpr(this, context);
1383
1410
  }
1411
+ clone() {
1412
+ return new LiteralExpr(this.value, this.type, this.sourceSpan);
1413
+ }
1384
1414
  }
1385
1415
  class TemplateLiteral {
1386
1416
  constructor(elements, expressions) {
1387
1417
  this.elements = elements;
1388
1418
  this.expressions = expressions;
1389
1419
  }
1420
+ clone() {
1421
+ return new TemplateLiteral(this.elements.map(el => el.clone()), this.expressions.map(expr => expr.clone()));
1422
+ }
1390
1423
  }
1391
1424
  class TemplateLiteralElement {
1392
1425
  constructor(text, sourceSpan, rawText) {
@@ -1401,6 +1434,9 @@ class TemplateLiteralElement {
1401
1434
  this.rawText =
1402
1435
  rawText ?? sourceSpan?.toString() ?? escapeForTemplateLiteral(escapeSlashes(text));
1403
1436
  }
1437
+ clone() {
1438
+ return new TemplateLiteralElement(this.text, this.sourceSpan, this.rawText);
1439
+ }
1404
1440
  }
1405
1441
  class LiteralPiece {
1406
1442
  constructor(text, sourceSpan) {
@@ -1445,6 +1481,9 @@ class LocalizedString extends Expression {
1445
1481
  visitExpression(visitor, context) {
1446
1482
  return visitor.visitLocalizedString(this, context);
1447
1483
  }
1484
+ clone() {
1485
+ return new LocalizedString(this.metaBlock, this.messageParts, this.placeHolderNames, this.expressions.map(expr => expr.clone()), this.sourceSpan);
1486
+ }
1448
1487
  /**
1449
1488
  * Serialize the given `meta` and `messagePart` into "cooked" and "raw" strings that can be used
1450
1489
  * in a `$localize` tagged string. The format of the metadata is the same as that parsed by
@@ -1546,6 +1585,9 @@ class ExternalExpr extends Expression {
1546
1585
  visitExpression(visitor, context) {
1547
1586
  return visitor.visitExternalExpr(this, context);
1548
1587
  }
1588
+ clone() {
1589
+ return new ExternalExpr(this.value, this.type, this.typeParams, this.sourceSpan);
1590
+ }
1549
1591
  }
1550
1592
  class ExternalReference {
1551
1593
  constructor(moduleName, name, runtime) {
@@ -1571,6 +1613,9 @@ class ConditionalExpr extends Expression {
1571
1613
  visitExpression(visitor, context) {
1572
1614
  return visitor.visitConditionalExpr(this, context);
1573
1615
  }
1616
+ clone() {
1617
+ return new ConditionalExpr(this.condition.clone(), this.trueCase.clone(), this.falseCase?.clone(), this.type, this.sourceSpan);
1618
+ }
1574
1619
  }
1575
1620
  class NotExpr extends Expression {
1576
1621
  constructor(condition, sourceSpan) {
@@ -1586,6 +1631,9 @@ class NotExpr extends Expression {
1586
1631
  visitExpression(visitor, context) {
1587
1632
  return visitor.visitNotExpr(this, context);
1588
1633
  }
1634
+ clone() {
1635
+ return new NotExpr(this.condition.clone(), this.sourceSpan);
1636
+ }
1589
1637
  }
1590
1638
  class FnParam {
1591
1639
  constructor(name, type = null) {
@@ -1595,6 +1643,9 @@ class FnParam {
1595
1643
  isEquivalent(param) {
1596
1644
  return this.name === param.name;
1597
1645
  }
1646
+ clone() {
1647
+ return new FnParam(this.name, this.type);
1648
+ }
1598
1649
  }
1599
1650
  class FunctionExpr extends Expression {
1600
1651
  constructor(params, statements, type, sourceSpan, name) {
@@ -1616,6 +1667,10 @@ class FunctionExpr extends Expression {
1616
1667
  toDeclStmt(name, modifiers) {
1617
1668
  return new DeclareFunctionStmt(name, this.params, this.statements, this.type, modifiers, this.sourceSpan);
1618
1669
  }
1670
+ clone() {
1671
+ // TODO: Should we deep clone statements?
1672
+ return new FunctionExpr(this.params.map(p => p.clone()), this.statements, this.type, this.sourceSpan, this.name);
1673
+ }
1619
1674
  }
1620
1675
  class UnaryOperatorExpr extends Expression {
1621
1676
  constructor(operator, expr, type, sourceSpan, parens = true) {
@@ -1634,6 +1689,9 @@ class UnaryOperatorExpr extends Expression {
1634
1689
  visitExpression(visitor, context) {
1635
1690
  return visitor.visitUnaryOperatorExpr(this, context);
1636
1691
  }
1692
+ clone() {
1693
+ return new UnaryOperatorExpr(this.operator, this.expr.clone(), this.type, this.sourceSpan, this.parens);
1694
+ }
1637
1695
  }
1638
1696
  class BinaryOperatorExpr extends Expression {
1639
1697
  constructor(operator, lhs, rhs, type, sourceSpan, parens = true) {
@@ -1653,6 +1711,9 @@ class BinaryOperatorExpr extends Expression {
1653
1711
  visitExpression(visitor, context) {
1654
1712
  return visitor.visitBinaryOperatorExpr(this, context);
1655
1713
  }
1714
+ clone() {
1715
+ return new BinaryOperatorExpr(this.operator, this.lhs.clone(), this.rhs.clone(), this.type, this.sourceSpan, this.parens);
1716
+ }
1656
1717
  }
1657
1718
  class ReadPropExpr extends Expression {
1658
1719
  constructor(receiver, name, type, sourceSpan) {
@@ -1673,6 +1734,9 @@ class ReadPropExpr extends Expression {
1673
1734
  set(value) {
1674
1735
  return new WritePropExpr(this.receiver, this.name, value, null, this.sourceSpan);
1675
1736
  }
1737
+ clone() {
1738
+ return new ReadPropExpr(this.receiver.clone(), this.name, this.type, this.sourceSpan);
1739
+ }
1676
1740
  }
1677
1741
  class ReadKeyExpr extends Expression {
1678
1742
  constructor(receiver, index, type, sourceSpan) {
@@ -1693,6 +1757,9 @@ class ReadKeyExpr extends Expression {
1693
1757
  set(value) {
1694
1758
  return new WriteKeyExpr(this.receiver, this.index, value, null, this.sourceSpan);
1695
1759
  }
1760
+ clone() {
1761
+ return new ReadKeyExpr(this.receiver, this.index.clone(), this.type, this.sourceSpan);
1762
+ }
1696
1763
  }
1697
1764
  class LiteralArrayExpr extends Expression {
1698
1765
  constructor(entries, type, sourceSpan) {
@@ -1708,6 +1775,9 @@ class LiteralArrayExpr extends Expression {
1708
1775
  visitExpression(visitor, context) {
1709
1776
  return visitor.visitLiteralArrayExpr(this, context);
1710
1777
  }
1778
+ clone() {
1779
+ return new LiteralArrayExpr(this.entries.map(e => e.clone()), this.type, this.sourceSpan);
1780
+ }
1711
1781
  }
1712
1782
  class LiteralMapEntry {
1713
1783
  constructor(key, value, quoted) {
@@ -1718,6 +1788,9 @@ class LiteralMapEntry {
1718
1788
  isEquivalent(e) {
1719
1789
  return this.key === e.key && this.value.isEquivalent(e.value);
1720
1790
  }
1791
+ clone() {
1792
+ return new LiteralMapEntry(this.key, this.value.clone(), this.quoted);
1793
+ }
1721
1794
  }
1722
1795
  class LiteralMapExpr extends Expression {
1723
1796
  constructor(entries, type, sourceSpan) {
@@ -1737,6 +1810,10 @@ class LiteralMapExpr extends Expression {
1737
1810
  visitExpression(visitor, context) {
1738
1811
  return visitor.visitLiteralMapExpr(this, context);
1739
1812
  }
1813
+ clone() {
1814
+ const entriesClone = this.entries.map(entry => entry.clone());
1815
+ return new LiteralMapExpr(entriesClone, this.type, this.sourceSpan);
1816
+ }
1740
1817
  }
1741
1818
  class CommaExpr extends Expression {
1742
1819
  constructor(parts, sourceSpan) {
@@ -1752,6 +1829,9 @@ class CommaExpr extends Expression {
1752
1829
  visitExpression(visitor, context) {
1753
1830
  return visitor.visitCommaExpr(this, context);
1754
1831
  }
1832
+ clone() {
1833
+ return new CommaExpr(this.parts.map(p => p.clone()));
1834
+ }
1755
1835
  }
1756
1836
  const NULL_EXPR = new LiteralExpr(null, null, null);
1757
1837
  const TYPED_NULL_EXPR = new LiteralExpr(null, INFERRED_TYPE, null);
@@ -2256,6 +2336,9 @@ class FixupExpression extends Expression {
2256
2336
  isConstant() {
2257
2337
  return true;
2258
2338
  }
2339
+ clone() {
2340
+ throw new Error(`Not supported.`);
2341
+ }
2259
2342
  fixup(expression) {
2260
2343
  this.resolved = expression;
2261
2344
  this.shared = true;
@@ -7247,6 +7330,7 @@ class InterpolationExpression extends Expression {
7247
7330
  this.isConstant = unsupported;
7248
7331
  this.isEquivalent = unsupported;
7249
7332
  this.visitExpression = unsupported;
7333
+ this.clone = unsupported;
7250
7334
  }
7251
7335
  }
7252
7336
  class DefaultLocalResolver {
@@ -8453,7 +8537,10 @@ class ElementAttributes {
8453
8537
  this.known.add(name);
8454
8538
  const array = this.arrayFor(kind);
8455
8539
  array.push(...getAttributeNameLiterals$1(name));
8456
- if (value !== null) {
8540
+ if (kind === ElementAttributeKind.Attribute || kind === ElementAttributeKind.Style) {
8541
+ if (value === null) {
8542
+ throw Error('Attribute & style element attributes must have a value');
8543
+ }
8457
8544
  array.push(value);
8458
8545
  }
8459
8546
  }
@@ -8556,6 +8643,10 @@ var OpKind;
8556
8643
  * An operation to instantiate a pipe.
8557
8644
  */
8558
8645
  OpKind[OpKind["Pipe"] = 16] = "Pipe";
8646
+ /**
8647
+ * An operation to associate an attribute with an element.
8648
+ */
8649
+ OpKind[OpKind["Attribute"] = 17] = "Attribute";
8559
8650
  })(OpKind || (OpKind = {}));
8560
8651
  /**
8561
8652
  * Distinguishes different kinds of IR expressions.
@@ -8610,6 +8701,22 @@ var ExpressionKind;
8610
8701
  * Binding to a pipe transformation with a variable number of arguments.
8611
8702
  */
8612
8703
  ExpressionKind[ExpressionKind["PipeBindingVariadic"] = 11] = "PipeBindingVariadic";
8704
+ /*
8705
+ * A safe property read requiring expansion into a null check.
8706
+ */
8707
+ ExpressionKind[ExpressionKind["SafePropertyRead"] = 12] = "SafePropertyRead";
8708
+ /**
8709
+ * A safe keyed read requiring expansion into a null check.
8710
+ */
8711
+ ExpressionKind[ExpressionKind["SafeKeyedRead"] = 13] = "SafeKeyedRead";
8712
+ /**
8713
+ * A safe function call requiring expansion into a null check.
8714
+ */
8715
+ ExpressionKind[ExpressionKind["SafeInvokeFunction"] = 14] = "SafeInvokeFunction";
8716
+ /**
8717
+ * An intermediate expression that will be expanded from a safe read into an explicit ternary.
8718
+ */
8719
+ ExpressionKind[ExpressionKind["SafeTernaryExpr"] = 15] = "SafeTernaryExpr";
8613
8720
  })(ExpressionKind || (ExpressionKind = {}));
8614
8721
  /**
8615
8722
  * Distinguishes between different kinds of `SemanticVariable`s.
@@ -8746,6 +8853,9 @@ class LexicalReadExpr extends ExpressionBase {
8746
8853
  return false;
8747
8854
  }
8748
8855
  transformInternalExpressions() { }
8856
+ clone() {
8857
+ return new LexicalReadExpr(this.name);
8858
+ }
8749
8859
  }
8750
8860
  /**
8751
8861
  * Runtime operation to retrieve the value of a local reference.
@@ -8768,6 +8878,11 @@ class ReferenceExpr extends ExpressionBase {
8768
8878
  return false;
8769
8879
  }
8770
8880
  transformInternalExpressions() { }
8881
+ clone() {
8882
+ const expr = new ReferenceExpr(this.target, this.offset);
8883
+ expr.slot = this.slot;
8884
+ return expr;
8885
+ }
8771
8886
  }
8772
8887
  /**
8773
8888
  * A reference to the current view context (usually the `ctx` variable in a template function).
@@ -8786,6 +8901,9 @@ class ContextExpr extends ExpressionBase {
8786
8901
  return false;
8787
8902
  }
8788
8903
  transformInternalExpressions() { }
8904
+ clone() {
8905
+ return new ContextExpr(this.view);
8906
+ }
8789
8907
  }
8790
8908
  /**
8791
8909
  * Runtime operation to navigate to the next view context in the view hierarchy.
@@ -8804,6 +8922,11 @@ class NextContextExpr extends ExpressionBase {
8804
8922
  return false;
8805
8923
  }
8806
8924
  transformInternalExpressions() { }
8925
+ clone() {
8926
+ const expr = new NextContextExpr();
8927
+ expr.steps = this.steps;
8928
+ return expr;
8929
+ }
8807
8930
  }
8808
8931
  /**
8809
8932
  * Runtime operation to snapshot the current view context.
@@ -8824,6 +8947,9 @@ class GetCurrentViewExpr extends ExpressionBase {
8824
8947
  return false;
8825
8948
  }
8826
8949
  transformInternalExpressions() { }
8950
+ clone() {
8951
+ return new GetCurrentViewExpr();
8952
+ }
8827
8953
  }
8828
8954
  /**
8829
8955
  * Runtime operation to restore a snapshotted view.
@@ -8858,6 +8984,9 @@ class RestoreViewExpr extends ExpressionBase {
8858
8984
  this.view = transformExpressionsInExpression(this.view, transform, flags);
8859
8985
  }
8860
8986
  }
8987
+ clone() {
8988
+ return new RestoreViewExpr(this.view instanceof Expression ? this.view.clone() : this.view);
8989
+ }
8861
8990
  }
8862
8991
  /**
8863
8992
  * Runtime operation to reset the current view context after `RestoreView`.
@@ -8880,6 +9009,9 @@ class ResetViewExpr extends ExpressionBase {
8880
9009
  transformInternalExpressions(transform, flags) {
8881
9010
  this.expr = transformExpressionsInExpression(this.expr, transform, flags);
8882
9011
  }
9012
+ clone() {
9013
+ return new ResetViewExpr(this.expr.clone());
9014
+ }
8883
9015
  }
8884
9016
  /**
8885
9017
  * Read of a variable declared as an `ir.VariableOp` and referenced through its `ir.XrefId`.
@@ -8899,6 +9031,11 @@ class ReadVariableExpr extends ExpressionBase {
8899
9031
  return false;
8900
9032
  }
8901
9033
  transformInternalExpressions() { }
9034
+ clone() {
9035
+ const expr = new ReadVariableExpr(this.xref);
9036
+ expr.name = this.name;
9037
+ return expr;
9038
+ }
8902
9039
  }
8903
9040
  class PureFunctionExpr extends ExpressionBase {
8904
9041
  static { _b = ConsumesVarsTrait, _c = UsesVarOffset; }
@@ -8944,6 +9081,12 @@ class PureFunctionExpr extends ExpressionBase {
8944
9081
  this.args[i] = transformExpressionsInExpression(this.args[i], transform, flags);
8945
9082
  }
8946
9083
  }
9084
+ clone() {
9085
+ const expr = new PureFunctionExpr(this.body?.clone() ?? null, this.args.map(arg => arg.clone()));
9086
+ expr.fn = this.fn?.clone() ?? null;
9087
+ expr.varOffset = this.varOffset;
9088
+ return expr;
9089
+ }
8947
9090
  }
8948
9091
  class PureFunctionParameterExpr extends ExpressionBase {
8949
9092
  constructor(index) {
@@ -8959,6 +9102,9 @@ class PureFunctionParameterExpr extends ExpressionBase {
8959
9102
  return true;
8960
9103
  }
8961
9104
  transformInternalExpressions() { }
9105
+ clone() {
9106
+ return new PureFunctionParameterExpr(this.index);
9107
+ }
8962
9108
  }
8963
9109
  class PipeBindingExpr extends ExpressionBase {
8964
9110
  static { _d = UsesSlotIndex, _e = ConsumesVarsTrait, _f = UsesVarOffset; }
@@ -8990,6 +9136,12 @@ class PipeBindingExpr extends ExpressionBase {
8990
9136
  this.args[idx] = transformExpressionsInExpression(this.args[idx], transform, flags);
8991
9137
  }
8992
9138
  }
9139
+ clone() {
9140
+ const r = new PipeBindingExpr(this.target, this.name, this.args.map(a => a.clone()));
9141
+ r.slot = this.slot;
9142
+ r.varOffset = this.varOffset;
9143
+ return r;
9144
+ }
8993
9145
  }
8994
9146
  class PipeBindingVariadicExpr extends ExpressionBase {
8995
9147
  static { _g = UsesSlotIndex, _h = ConsumesVarsTrait, _j = UsesVarOffset; }
@@ -9018,6 +9170,101 @@ class PipeBindingVariadicExpr extends ExpressionBase {
9018
9170
  transformInternalExpressions(transform, flags) {
9019
9171
  this.args = transformExpressionsInExpression(this.args, transform, flags);
9020
9172
  }
9173
+ clone() {
9174
+ const r = new PipeBindingVariadicExpr(this.target, this.name, this.args.clone(), this.numArgs);
9175
+ r.slot = this.slot;
9176
+ r.varOffset = this.varOffset;
9177
+ return r;
9178
+ }
9179
+ }
9180
+ class SafePropertyReadExpr extends ExpressionBase {
9181
+ constructor(receiver, name) {
9182
+ super();
9183
+ this.receiver = receiver;
9184
+ this.name = name;
9185
+ this.kind = ExpressionKind.SafePropertyRead;
9186
+ }
9187
+ visitExpression(visitor, context) { }
9188
+ isEquivalent() {
9189
+ return false;
9190
+ }
9191
+ isConstant() {
9192
+ return false;
9193
+ }
9194
+ transformInternalExpressions(transform, flags) {
9195
+ this.receiver = transformExpressionsInExpression(this.receiver, transform, flags);
9196
+ }
9197
+ clone() {
9198
+ return new SafePropertyReadExpr(this.receiver.clone(), this.name);
9199
+ }
9200
+ }
9201
+ class SafeKeyedReadExpr extends ExpressionBase {
9202
+ constructor(receiver, index) {
9203
+ super();
9204
+ this.receiver = receiver;
9205
+ this.index = index;
9206
+ this.kind = ExpressionKind.SafeKeyedRead;
9207
+ }
9208
+ visitExpression(visitor, context) { }
9209
+ isEquivalent() {
9210
+ return false;
9211
+ }
9212
+ isConstant() {
9213
+ return false;
9214
+ }
9215
+ transformInternalExpressions(transform, flags) {
9216
+ this.receiver = transformExpressionsInExpression(this.receiver, transform, flags);
9217
+ this.index = transformExpressionsInExpression(this.index, transform, flags);
9218
+ }
9219
+ clone() {
9220
+ return new SafeKeyedReadExpr(this.receiver.clone(), this.index.clone());
9221
+ }
9222
+ }
9223
+ class SafeInvokeFunctionExpr extends ExpressionBase {
9224
+ constructor(receiver, args) {
9225
+ super();
9226
+ this.receiver = receiver;
9227
+ this.args = args;
9228
+ this.kind = ExpressionKind.SafeInvokeFunction;
9229
+ }
9230
+ visitExpression(visitor, context) { }
9231
+ isEquivalent() {
9232
+ return false;
9233
+ }
9234
+ isConstant() {
9235
+ return false;
9236
+ }
9237
+ transformInternalExpressions(transform, flags) {
9238
+ this.receiver = transformExpressionsInExpression(this.receiver, transform, flags);
9239
+ for (let i = 0; i < this.args.length; i++) {
9240
+ this.args[i] = transformExpressionsInExpression(this.args[i], transform, flags);
9241
+ }
9242
+ }
9243
+ clone() {
9244
+ return new SafeInvokeFunctionExpr(this.receiver.clone(), this.args.map(a => a.clone()));
9245
+ }
9246
+ }
9247
+ class SafeTernaryExpr extends ExpressionBase {
9248
+ constructor(guard, expr) {
9249
+ super();
9250
+ this.guard = guard;
9251
+ this.expr = expr;
9252
+ this.kind = ExpressionKind.SafeTernaryExpr;
9253
+ }
9254
+ visitExpression(visitor, context) { }
9255
+ isEquivalent() {
9256
+ return false;
9257
+ }
9258
+ isConstant() {
9259
+ return false;
9260
+ }
9261
+ transformInternalExpressions(transform, flags) {
9262
+ this.guard = transformExpressionsInExpression(this.guard, transform, flags);
9263
+ this.expr = transformExpressionsInExpression(this.expr, transform, flags);
9264
+ }
9265
+ clone() {
9266
+ return new SafeTernaryExpr(this.guard.clone(), this.expr.clone());
9267
+ }
9021
9268
  }
9022
9269
  /**
9023
9270
  * Visits all `Expression`s in the AST of `op` with the `visitor` function.
@@ -9052,6 +9299,11 @@ function transformExpressionsInOp(op, transform, flags) {
9052
9299
  case OpKind.Statement:
9053
9300
  transformExpressionsInStatement(op.statement, transform, flags);
9054
9301
  break;
9302
+ case OpKind.Attribute:
9303
+ if (op.value) {
9304
+ transformExpressionsInExpression(op.value, transform, flags);
9305
+ }
9306
+ break;
9055
9307
  case OpKind.Variable:
9056
9308
  op.initializer = transformExpressionsInExpression(op.initializer, transform, flags);
9057
9309
  break;
@@ -9421,6 +9673,18 @@ const NEW_OP = {
9421
9673
  next: null,
9422
9674
  };
9423
9675
 
9676
+ /**
9677
+ * The set of OpKinds that represent the creation of an element or container
9678
+ */
9679
+ const elementContainerOpKinds = new Set([
9680
+ OpKind.Element, OpKind.ElementStart, OpKind.Container, OpKind.ContainerStart, OpKind.Template
9681
+ ]);
9682
+ /**
9683
+ * Checks whether the given operation represents the creation of an element or container.
9684
+ */
9685
+ function isElementOrContainerOp(op) {
9686
+ return elementContainerOpKinds.has(op.kind);
9687
+ }
9424
9688
  /**
9425
9689
  * Create an `ElementStartOp`.
9426
9690
  */
@@ -9515,10 +9779,11 @@ function createInterpolateTextOp(xref, strings, expressions) {
9515
9779
  /**
9516
9780
  * Create a `PropertyOp`.
9517
9781
  */
9518
- function createPropertyOp(xref, name, expression) {
9782
+ function createPropertyOp(xref, bindingKind, name, expression) {
9519
9783
  return {
9520
9784
  kind: OpKind.Property,
9521
9785
  target: xref,
9786
+ bindingKind,
9522
9787
  name,
9523
9788
  expression,
9524
9789
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
@@ -9526,13 +9791,27 @@ function createPropertyOp(xref, name, expression) {
9526
9791
  ...NEW_OP,
9527
9792
  };
9528
9793
  }
9794
+ /**
9795
+ * Create an `AttributeOp`.
9796
+ */
9797
+ function createAttributeOp(target, attributeKind, name, value) {
9798
+ return {
9799
+ kind: OpKind.Attribute,
9800
+ target,
9801
+ attributeKind,
9802
+ name,
9803
+ value,
9804
+ ...NEW_OP,
9805
+ };
9806
+ }
9529
9807
  /**
9530
9808
  * Create a `InterpolateProperty`.
9531
9809
  */
9532
- function createInterpolatePropertyOp(xref, name, strings, expressions) {
9810
+ function createInterpolatePropertyOp(xref, bindingKind, name, strings, expressions) {
9533
9811
  return {
9534
9812
  kind: OpKind.InterpolateProperty,
9535
9813
  target: xref,
9814
+ bindingKind,
9536
9815
  name,
9537
9816
  strings,
9538
9817
  expressions,
@@ -9553,62 +9832,302 @@ function createAdvanceOp(delta) {
9553
9832
  }
9554
9833
 
9555
9834
  /**
9556
- * Converts the semantic attributes of element-like operations (elements, templates) into constant
9557
- * array expressions, and lifts them into the overall component `consts`.
9835
+ * Counts the number of variable slots used within each view, and stores that on the view itself, as
9836
+ * well as propagates it to the `ir.TemplateOp` for embedded views.
9558
9837
  */
9559
- function phaseConstCollection(cpl) {
9838
+ function phaseVarCounting(cpl) {
9839
+ // First, count the vars used in each view, and update the view-level counter.
9560
9840
  for (const [_, view] of cpl.views) {
9561
- for (const op of view.create) {
9562
- if (op.kind !== OpKind.ElementStart && op.kind !== OpKind.Element &&
9563
- op.kind !== OpKind.Template) {
9564
- continue;
9841
+ let varCount = 0;
9842
+ for (const op of view.ops()) {
9843
+ if (hasConsumesVarsTrait(op)) {
9844
+ varCount += varsUsedByOp(op);
9565
9845
  }
9566
- else if (!(op.attributes instanceof ElementAttributes)) {
9846
+ visitExpressionsInOp(op, expr => {
9847
+ if (!isIrExpression(expr)) {
9848
+ return;
9849
+ }
9850
+ // Some expressions require knowledge of the number of variable slots consumed.
9851
+ if (hasUsesVarOffsetTrait(expr)) {
9852
+ expr.varOffset = varCount;
9853
+ }
9854
+ if (hasConsumesVarsTrait(expr)) {
9855
+ varCount += varsUsedByIrExpression(expr);
9856
+ }
9857
+ });
9858
+ }
9859
+ view.vars = varCount;
9860
+ }
9861
+ // Add var counts for each view to the `ir.TemplateOp` which declares that view (if the view is an
9862
+ // embedded view).
9863
+ for (const [_, view] of cpl.views) {
9864
+ for (const op of view.create) {
9865
+ if (op.kind !== OpKind.Template) {
9567
9866
  continue;
9568
9867
  }
9569
- const attrArray = serializeAttributes(op.attributes);
9570
- if (attrArray.entries.length > 0) {
9571
- op.attributes = cpl.addConst(attrArray);
9572
- }
9573
- else {
9574
- op.attributes = null;
9575
- }
9868
+ const childView = cpl.views.get(op.xref);
9869
+ op.vars = childView.vars;
9576
9870
  }
9577
9871
  }
9578
9872
  }
9579
- function serializeAttributes({ attributes, bindings, classes, i18n, projectAs, styles, template }) {
9580
- const attrArray = [...attributes];
9581
- if (projectAs !== null) {
9582
- attrArray.push(literal(5 /* core.AttributeMarker.ProjectAs */), literal(projectAs));
9583
- }
9584
- if (classes.length > 0) {
9585
- attrArray.push(literal(1 /* core.AttributeMarker.Classes */), ...classes);
9586
- }
9587
- if (styles.length > 0) {
9588
- attrArray.push(literal(2 /* core.AttributeMarker.Styles */), ...styles);
9589
- }
9590
- if (bindings.length > 0) {
9591
- attrArray.push(literal(3 /* core.AttributeMarker.Bindings */), ...bindings);
9592
- }
9593
- if (template.length > 0) {
9594
- attrArray.push(literal(4 /* core.AttributeMarker.Template */), ...template);
9595
- }
9596
- if (i18n.length > 0) {
9597
- attrArray.push(literal(6 /* core.AttributeMarker.I18n */), ...i18n);
9598
- }
9599
- return literalArr(attrArray);
9600
- }
9601
-
9602
- const REPLACEMENTS = new Map([
9603
- [OpKind.ElementEnd, [OpKind.ElementStart, OpKind.Element]],
9604
- [OpKind.ContainerEnd, [OpKind.ContainerStart, OpKind.Container]],
9605
- ]);
9606
9873
  /**
9607
- * Replace sequences of mergable elements (e.g. `ElementStart` and `ElementEnd`) with a consolidated
9608
- * element (e.g. `Element`).
9874
+ * Different operations that implement `ir.UsesVarsTrait` use different numbers of variables, so
9875
+ * count the variables used by any particular `op`.
9609
9876
  */
9610
- function phaseEmptyElements(cpl) {
9611
- for (const [_, view] of cpl.views) {
9877
+ function varsUsedByOp(op) {
9878
+ switch (op.kind) {
9879
+ case OpKind.Property:
9880
+ // Property bindings use 1 variable slot.
9881
+ return 1;
9882
+ case OpKind.InterpolateText:
9883
+ // `ir.InterpolateTextOp`s use a variable slot for each dynamic expression.
9884
+ return op.expressions.length;
9885
+ case OpKind.InterpolateProperty:
9886
+ // `ir.InterpolatePropertyOp`s use a variable slot for each dynamic expression, plus one for
9887
+ // the result.
9888
+ return 1 + op.expressions.length;
9889
+ default:
9890
+ throw new Error(`Unhandled op: ${OpKind[op.kind]}`);
9891
+ }
9892
+ }
9893
+ function varsUsedByIrExpression(expr) {
9894
+ switch (expr.kind) {
9895
+ case ExpressionKind.PureFunctionExpr:
9896
+ return 1 + expr.args.length;
9897
+ case ExpressionKind.PipeBinding:
9898
+ return 1 + expr.args.length;
9899
+ case ExpressionKind.PipeBindingVariadic:
9900
+ return 1 + expr.numArgs;
9901
+ default:
9902
+ throw new Error(`AssertionError: unhandled ConsumesVarsTrait expression ${expr.constructor.name}`);
9903
+ }
9904
+ }
9905
+
9906
+ function phaseAlignPipeVariadicVarOffset(cpl) {
9907
+ for (const view of cpl.views.values()) {
9908
+ for (const op of view.update) {
9909
+ visitExpressionsInOp(op, expr => {
9910
+ if (!(expr instanceof PipeBindingVariadicExpr)) {
9911
+ return expr;
9912
+ }
9913
+ if (!(expr.args instanceof PureFunctionExpr)) {
9914
+ return expr;
9915
+ }
9916
+ if (expr.varOffset === null || expr.args.varOffset === null) {
9917
+ throw new Error(`Must run after variable counting`);
9918
+ }
9919
+ // The structure of this variadic pipe expression is:
9920
+ // PipeBindingVariadic(#, Y, PureFunction(X, ...ARGS))
9921
+ // Where X and Y are the slot offsets for the variables used by these operations, and Y > X.
9922
+ // In `TemplateDefinitionBuilder` the PipeBindingVariadic variable slots are allocated
9923
+ // before the PureFunction slots, which is unusually out-of-order.
9924
+ //
9925
+ // To maintain identical output for the tests in question, we adjust the variable offsets of
9926
+ // these two calls to emulate TDB's behavior. This is not perfect, because the ARGS of the
9927
+ // PureFunction call may also allocate slots which by TDB's ordering would come after X, and
9928
+ // we don't account for that. Still, this should be enough to pass the existing pipe tests.
9929
+ // Put the PipeBindingVariadic vars where the PureFunction vars were previously allocated.
9930
+ expr.varOffset = expr.args.varOffset;
9931
+ // Put the PureFunction vars following the PipeBindingVariadic vars.
9932
+ expr.args.varOffset = expr.varOffset + varsUsedByIrExpression(expr);
9933
+ });
9934
+ }
9935
+ }
9936
+ }
9937
+
9938
+ /**
9939
+ * Looks up an element in the given map by xref ID.
9940
+ */
9941
+ function lookupElement(elements, xref) {
9942
+ const el = elements.get(xref);
9943
+ if (el === undefined) {
9944
+ throw new Error('All attributes should have an element-like target.');
9945
+ }
9946
+ return el;
9947
+ }
9948
+ /**
9949
+ * Find all attribute and binding ops, and collect them into the ElementAttribute structures.
9950
+ * In cases where no instruction needs to be generated for the attribute or binding, it is removed.
9951
+ */
9952
+ function phaseAttributeExtraction(cpl, compatibility) {
9953
+ for (const [_, view] of cpl.views) {
9954
+ populateElementAttributes(view, compatibility);
9955
+ }
9956
+ }
9957
+ /**
9958
+ * Populates the ElementAttributes map for the given view, and removes ops for any bindings that do
9959
+ * not need further processing.
9960
+ */
9961
+ function populateElementAttributes(view, compatibility) {
9962
+ const elements = new Map();
9963
+ for (const op of view.create) {
9964
+ if (!isElementOrContainerOp(op)) {
9965
+ continue;
9966
+ }
9967
+ elements.set(op.xref, op);
9968
+ }
9969
+ for (const op of view.ops()) {
9970
+ let ownerOp;
9971
+ switch (op.kind) {
9972
+ case OpKind.Attribute:
9973
+ ownerOp = lookupElement(elements, op.target);
9974
+ assertIsElementAttributes(ownerOp.attributes);
9975
+ let extractable = compatibility ?
9976
+ (op.value instanceof LiteralExpr && typeof op.value.value === 'string') :
9977
+ (op.value.isConstant());
9978
+ // We don't need to generate instructions for attributes that can be extracted as consts.
9979
+ if (extractable) {
9980
+ ownerOp.attributes.add(op.attributeKind, op.name, op.value);
9981
+ OpList.remove(op);
9982
+ }
9983
+ break;
9984
+ case OpKind.Property:
9985
+ case OpKind.InterpolateProperty:
9986
+ ownerOp = lookupElement(elements, op.target);
9987
+ assertIsElementAttributes(ownerOp.attributes);
9988
+ ownerOp.attributes.add(op.bindingKind, op.name, null);
9989
+ break;
9990
+ case OpKind.Listener:
9991
+ ownerOp = lookupElement(elements, op.target);
9992
+ assertIsElementAttributes(ownerOp.attributes);
9993
+ ownerOp.attributes.add(ElementAttributeKind.Binding, op.name, null);
9994
+ // We don't need to generate instructions for listeners on templates.
9995
+ if (ownerOp.kind === OpKind.Template) {
9996
+ OpList.remove(op);
9997
+ }
9998
+ break;
9999
+ }
10000
+ }
10001
+ }
10002
+
10003
+ const CHAINABLE = new Set([
10004
+ Identifiers.elementStart,
10005
+ Identifiers.elementEnd,
10006
+ Identifiers.property,
10007
+ Identifiers.elementContainerStart,
10008
+ Identifiers.elementContainerEnd,
10009
+ Identifiers.elementContainer,
10010
+ ]);
10011
+ /**
10012
+ * Post-process a reified view compilation and convert sequential calls to chainable instructions
10013
+ * into chain calls.
10014
+ *
10015
+ * For example, two `elementStart` operations in sequence:
10016
+ *
10017
+ * ```typescript
10018
+ * elementStart(0, 'div');
10019
+ * elementStart(1, 'span');
10020
+ * ```
10021
+ *
10022
+ * Can be called as a chain instead:
10023
+ *
10024
+ * ```typescript
10025
+ * elementStart(0, 'div')(1, 'span');
10026
+ * ```
10027
+ */
10028
+ function phaseChaining(cpl) {
10029
+ for (const [_, view] of cpl.views) {
10030
+ chainOperationsInList(view.create);
10031
+ chainOperationsInList(view.update);
10032
+ }
10033
+ }
10034
+ function chainOperationsInList(opList) {
10035
+ let chain = null;
10036
+ for (const op of opList) {
10037
+ if (op.kind !== OpKind.Statement || !(op.statement instanceof ExpressionStatement)) {
10038
+ // This type of statement isn't chainable.
10039
+ chain = null;
10040
+ continue;
10041
+ }
10042
+ if (!(op.statement.expr instanceof InvokeFunctionExpr) ||
10043
+ !(op.statement.expr.fn instanceof ExternalExpr)) {
10044
+ // This is a statement, but not an instruction-type call, so not chainable.
10045
+ chain = null;
10046
+ continue;
10047
+ }
10048
+ const instruction = op.statement.expr.fn.value;
10049
+ if (!CHAINABLE.has(instruction)) {
10050
+ // This instruction isn't chainable.
10051
+ chain = null;
10052
+ continue;
10053
+ }
10054
+ // This instruction can be chained. It can either be added on to the previous chain (if
10055
+ // compatible) or it can be the start of a new chain.
10056
+ if (chain !== null && chain.instruction === instruction) {
10057
+ // This instruction can be added onto the previous chain.
10058
+ const expression = chain.expression.callFn(op.statement.expr.args, op.statement.expr.sourceSpan, op.statement.expr.pure);
10059
+ chain.expression = expression;
10060
+ chain.op.statement = expression.toStmt();
10061
+ OpList.remove(op);
10062
+ }
10063
+ else {
10064
+ // Leave this instruction alone for now, but consider it the start of a new chain.
10065
+ chain = {
10066
+ op,
10067
+ instruction,
10068
+ expression: op.statement.expr,
10069
+ };
10070
+ }
10071
+ }
10072
+ }
10073
+
10074
+ /**
10075
+ * Converts the semantic attributes of element-like operations (elements, templates) into constant
10076
+ * array expressions, and lifts them into the overall component `consts`.
10077
+ */
10078
+ function phaseConstCollection(cpl) {
10079
+ for (const [_, view] of cpl.views) {
10080
+ for (const op of view.create) {
10081
+ if (op.kind !== OpKind.ElementStart && op.kind !== OpKind.Element &&
10082
+ op.kind !== OpKind.Template) {
10083
+ continue;
10084
+ }
10085
+ else if (!(op.attributes instanceof ElementAttributes)) {
10086
+ continue;
10087
+ }
10088
+ const attrArray = serializeAttributes(op.attributes);
10089
+ if (attrArray.entries.length > 0) {
10090
+ op.attributes = cpl.addConst(attrArray);
10091
+ }
10092
+ else {
10093
+ op.attributes = null;
10094
+ }
10095
+ }
10096
+ }
10097
+ }
10098
+ function serializeAttributes({ attributes, bindings, classes, i18n, projectAs, styles, template }) {
10099
+ const attrArray = [...attributes];
10100
+ if (projectAs !== null) {
10101
+ attrArray.push(literal(5 /* core.AttributeMarker.ProjectAs */), literal(projectAs));
10102
+ }
10103
+ if (classes.length > 0) {
10104
+ attrArray.push(literal(1 /* core.AttributeMarker.Classes */), ...classes);
10105
+ }
10106
+ if (styles.length > 0) {
10107
+ attrArray.push(literal(2 /* core.AttributeMarker.Styles */), ...styles);
10108
+ }
10109
+ if (bindings.length > 0) {
10110
+ attrArray.push(literal(3 /* core.AttributeMarker.Bindings */), ...bindings);
10111
+ }
10112
+ if (template.length > 0) {
10113
+ attrArray.push(literal(4 /* core.AttributeMarker.Template */), ...template);
10114
+ }
10115
+ if (i18n.length > 0) {
10116
+ attrArray.push(literal(6 /* core.AttributeMarker.I18n */), ...i18n);
10117
+ }
10118
+ return literalArr(attrArray);
10119
+ }
10120
+
10121
+ const REPLACEMENTS = new Map([
10122
+ [OpKind.ElementEnd, [OpKind.ElementStart, OpKind.Element]],
10123
+ [OpKind.ContainerEnd, [OpKind.ContainerStart, OpKind.Container]],
10124
+ ]);
10125
+ /**
10126
+ * Replace sequences of mergable elements (e.g. `ElementStart` and `ElementEnd`) with a consolidated
10127
+ * element (e.g. `Element`).
10128
+ */
10129
+ function phaseEmptyElements(cpl) {
10130
+ for (const [_, view] of cpl.views) {
9612
10131
  for (const op of view.create) {
9613
10132
  const opReplacements = REPLACEMENTS.get(op.kind);
9614
10133
  if (opReplacements === undefined) {
@@ -9674,804 +10193,961 @@ function phaseGenerateAdvance(cpl) {
9674
10193
  }
9675
10194
  }
9676
10195
 
9677
- // This file contains helpers for generating calls to Ivy instructions. In particular, each
9678
- // instruction type is represented as a function, which may select a specific instruction variant
9679
- // depending on the exact arguments.
9680
- function element(slot, tag, constIndex, localRefIndex) {
9681
- return elementOrContainerBase(Identifiers.element, slot, tag, constIndex, localRefIndex);
9682
- }
9683
- function elementStart(slot, tag, constIndex, localRefIndex) {
9684
- return elementOrContainerBase(Identifiers.elementStart, slot, tag, constIndex, localRefIndex);
9685
- }
9686
- function elementOrContainerBase(instruction, slot, tag, constIndex, localRefIndex) {
9687
- const args = [literal(slot)];
9688
- if (tag !== null) {
9689
- args.push(literal(tag));
9690
- }
9691
- if (localRefIndex !== null) {
9692
- args.push(literal(constIndex), // might be null, but that's okay.
9693
- literal(localRefIndex));
9694
- }
9695
- else if (constIndex !== null) {
9696
- args.push(literal(constIndex));
10196
+ function phaseNullishCoalescing(cpl) {
10197
+ for (const view of cpl.views.values()) {
10198
+ for (const op of view.ops()) {
10199
+ transformExpressionsInOp(op, expr => {
10200
+ if (!(expr instanceof BinaryOperatorExpr) ||
10201
+ expr.operator !== BinaryOperator.NullishCoalesce) {
10202
+ return expr;
10203
+ }
10204
+ // TODO: We need to unconditionally emit a temporary variable to match
10205
+ // TemplateDefinitionBuilder. (We could also emit one conditionally when not in
10206
+ // compatibility mode.)
10207
+ return new ConditionalExpr(new BinaryOperatorExpr(BinaryOperator.And, new BinaryOperatorExpr(BinaryOperator.NotIdentical, expr.lhs, NULL_EXPR), new BinaryOperatorExpr(BinaryOperator.NotIdentical, expr.lhs, new LiteralExpr(undefined))), expr.lhs, expr.rhs);
10208
+ }, VisitorContextFlag.None);
10209
+ }
9697
10210
  }
9698
- return call(instruction, args);
9699
- }
9700
- function elementEnd() {
9701
- return call(Identifiers.elementEnd, []);
9702
- }
9703
- function elementContainerStart(slot, constIndex, localRefIndex) {
9704
- return elementOrContainerBase(Identifiers.elementContainerStart, slot, /* tag */ null, constIndex, localRefIndex);
9705
10211
  }
9706
- function elementContainer(slot, constIndex, localRefIndex) {
9707
- return elementOrContainerBase(Identifiers.elementContainer, slot, /* tag */ null, constIndex, localRefIndex);
9708
- }
9709
- function elementContainerEnd() {
9710
- return call(Identifiers.elementContainerEnd, []);
9711
- }
9712
- function template(slot, templateFnRef, decls, vars, tag, constIndex) {
9713
- return call(Identifiers.templateCreate, [
9714
- literal(slot),
9715
- templateFnRef,
9716
- literal(decls),
9717
- literal(vars),
9718
- literal(tag),
9719
- literal(constIndex),
9720
- ]);
9721
- }
9722
- function listener(name, handlerFn) {
9723
- return call(Identifiers.listener, [
9724
- literal(name),
9725
- handlerFn,
9726
- ]);
9727
- }
9728
- function pipe(slot, name) {
9729
- return call(Identifiers.pipe, [
9730
- literal(slot),
9731
- literal(name),
9732
- ]);
9733
- }
9734
- function advance(delta) {
9735
- return call(Identifiers.advance, [
9736
- literal(delta),
9737
- ]);
9738
- }
9739
- function reference(slot) {
9740
- return importExpr(Identifiers.reference).callFn([
9741
- literal(slot),
9742
- ]);
9743
- }
9744
- function nextContext(steps) {
9745
- return importExpr(Identifiers.nextContext).callFn(steps === 1 ? [] : [literal(steps)]);
9746
- }
9747
- function getCurrentView() {
9748
- return importExpr(Identifiers.getCurrentView).callFn([]);
9749
- }
9750
- function restoreView(savedView) {
9751
- return importExpr(Identifiers.restoreView).callFn([
9752
- savedView,
9753
- ]);
9754
- }
9755
- function resetView(returnValue) {
9756
- return importExpr(Identifiers.resetView).callFn([
9757
- returnValue,
9758
- ]);
10212
+
10213
+ /**
10214
+ * Generate a preamble sequence for each view creation block and listener function which declares
10215
+ * any variables that be referenced in other operations in the block.
10216
+ *
10217
+ * Variables generated include:
10218
+ * * a saved view context to be used to restore the current view in event listeners.
10219
+ * * the context of the restored view within event listener handlers.
10220
+ * * context variables from the current view as well as all parent views (including the root
10221
+ * context if needed).
10222
+ * * local references from elements within the current view and any lexical parents.
10223
+ *
10224
+ * Variables are generated here unconditionally, and may optimized away in future operations if it
10225
+ * turns out their values (and any side effects) are unused.
10226
+ */
10227
+ function phaseGenerateVariables(cpl) {
10228
+ recursivelyProcessView(cpl.root, /* there is no parent scope for the root view */ null);
9759
10229
  }
9760
- function text(slot, initialValue) {
9761
- const args = [literal(slot)];
9762
- if (initialValue !== '') {
9763
- args.push(literal(initialValue));
10230
+ /**
10231
+ * Process the given `ViewCompilation` and generate preambles for it and any listeners that it
10232
+ * declares.
10233
+ *
10234
+ * @param `parentScope` a scope extracted from the parent view which captures any variables which
10235
+ * should be inherited by this view. `null` if the current view is the root view.
10236
+ */
10237
+ function recursivelyProcessView(view, parentScope) {
10238
+ // Extract a `Scope` from this view.
10239
+ const scope = getScopeForView(view, parentScope);
10240
+ // Embedded views require an operation to save/restore the view context.
10241
+ if (view.parent !== null) {
10242
+ // Start the view creation block with an operation to save the current view context. This may be
10243
+ // used to restore the view context in any listeners that may be present.
9764
10244
  }
9765
- return call(Identifiers.text, args);
9766
- }
9767
- function property(name, expression) {
9768
- return call(Identifiers.property, [
9769
- literal(name),
9770
- expression,
9771
- ]);
9772
- }
9773
- const PIPE_BINDINGS = [
9774
- Identifiers.pipeBind1,
9775
- Identifiers.pipeBind2,
9776
- Identifiers.pipeBind3,
9777
- Identifiers.pipeBind4,
9778
- ];
9779
- function pipeBind(slot, varOffset, args) {
9780
- if (args.length < 1 || args.length > PIPE_BINDINGS.length) {
9781
- throw new Error(`pipeBind() argument count out of bounds`);
10245
+ for (const op of view.create) {
10246
+ switch (op.kind) {
10247
+ case OpKind.Template:
10248
+ // Descend into child embedded views.
10249
+ recursivelyProcessView(view.tpl.views.get(op.xref), scope);
10250
+ break;
10251
+ case OpKind.Listener:
10252
+ // Prepend variables to listener handler functions.
10253
+ op.handlerOps.prepend(generateVariablesInScopeForView(view, scope));
10254
+ break;
10255
+ }
9782
10256
  }
9783
- const instruction = PIPE_BINDINGS[args.length - 1];
9784
- return importExpr(instruction).callFn([
9785
- literal(slot),
9786
- literal(varOffset),
9787
- ...args,
9788
- ]);
9789
- }
9790
- function pipeBindV(slot, varOffset, args) {
9791
- return importExpr(Identifiers.pipeBindV).callFn([
9792
- literal(slot),
9793
- literal(varOffset),
9794
- args,
9795
- ]);
10257
+ // Prepend the declarations for all available variables in scope to the `update` block.
10258
+ const preambleOps = generateVariablesInScopeForView(view, scope);
10259
+ view.update.prepend(preambleOps);
9796
10260
  }
9797
- function textInterpolate(strings, expressions) {
9798
- if (strings.length < 1 || expressions.length !== strings.length - 1) {
9799
- throw new Error(`AssertionError: expected specific shape of args for strings/expressions in interpolation`);
9800
- }
9801
- const interpolationArgs = [];
9802
- if (expressions.length === 1 && strings[0] === '' && strings[1] === '') {
9803
- interpolationArgs.push(expressions[0]);
10261
+ /**
10262
+ * Process a view and generate a `Scope` representing the variables available for reference within
10263
+ * that view.
10264
+ */
10265
+ function getScopeForView(view, parent) {
10266
+ const scope = {
10267
+ view: view.xref,
10268
+ viewContextVariable: {
10269
+ kind: SemanticVariableKind.Context,
10270
+ name: null,
10271
+ view: view.xref,
10272
+ },
10273
+ contextVariables: new Map(),
10274
+ references: [],
10275
+ parent,
10276
+ };
10277
+ for (const identifier of view.contextVariables.keys()) {
10278
+ scope.contextVariables.set(identifier, {
10279
+ kind: SemanticVariableKind.Identifier,
10280
+ name: null,
10281
+ identifier,
10282
+ });
9804
10283
  }
9805
- else {
9806
- let idx;
9807
- for (idx = 0; idx < expressions.length; idx++) {
9808
- interpolationArgs.push(literal(strings[idx]), expressions[idx]);
10284
+ for (const op of view.create) {
10285
+ switch (op.kind) {
10286
+ case OpKind.Element:
10287
+ case OpKind.ElementStart:
10288
+ case OpKind.Template:
10289
+ if (!Array.isArray(op.localRefs)) {
10290
+ throw new Error(`AssertionError: expected localRefs to be an array`);
10291
+ }
10292
+ // Record available local references from this element.
10293
+ for (let offset = 0; offset < op.localRefs.length; offset++) {
10294
+ scope.references.push({
10295
+ name: op.localRefs[offset].name,
10296
+ targetId: op.xref,
10297
+ offset,
10298
+ variable: {
10299
+ kind: SemanticVariableKind.Identifier,
10300
+ name: null,
10301
+ identifier: op.localRefs[offset].name,
10302
+ },
10303
+ });
10304
+ }
10305
+ break;
9809
10306
  }
9810
- // idx points at the last string.
9811
- interpolationArgs.push(literal(strings[idx]));
9812
10307
  }
9813
- return callVariadicInstruction(TEXT_INTERPOLATE_CONFIG, [], interpolationArgs);
10308
+ return scope;
9814
10309
  }
9815
- function propertyInterpolate(name, strings, expressions) {
9816
- if (strings.length < 1 || expressions.length !== strings.length - 1) {
9817
- throw new Error(`AssertionError: expected specific shape of args for strings/expressions in interpolation`);
10310
+ /**
10311
+ * Generate declarations for all variables that are in scope for a given view.
10312
+ *
10313
+ * This is a recursive process, as views inherit variables available from their parent view, which
10314
+ * itself may have inherited variables, etc.
10315
+ */
10316
+ function generateVariablesInScopeForView(view, scope) {
10317
+ const newOps = [];
10318
+ if (scope.view !== view.xref) {
10319
+ // Before generating variables for a parent view, we need to switch to the context of the parent
10320
+ // view with a `nextContext` expression. This context switching operation itself declares a
10321
+ // variable, because the context of the view may be referenced directly.
10322
+ newOps.push(createVariableOp(view.tpl.allocateXrefId(), scope.viewContextVariable, new NextContextExpr()));
9818
10323
  }
9819
- const interpolationArgs = [];
9820
- if (expressions.length === 1 && strings[0] === '' && strings[1] === '') {
9821
- interpolationArgs.push(expressions[0]);
10324
+ // Add variables for all context variables available in this scope's view.
10325
+ for (const [name, value] of view.tpl.views.get(scope.view).contextVariables) {
10326
+ newOps.push(createVariableOp(view.tpl.allocateXrefId(), scope.contextVariables.get(name), new ReadPropExpr(new ContextExpr(scope.view), value)));
9822
10327
  }
9823
- else {
9824
- let idx;
9825
- for (idx = 0; idx < expressions.length; idx++) {
9826
- interpolationArgs.push(literal(strings[idx]), expressions[idx]);
9827
- }
9828
- // idx points at the last string.
9829
- interpolationArgs.push(literal(strings[idx]));
10328
+ // Add variables for all local references declared for elements in this scope.
10329
+ for (const ref of scope.references) {
10330
+ newOps.push(createVariableOp(view.tpl.allocateXrefId(), ref.variable, new ReferenceExpr(ref.targetId, ref.offset)));
9830
10331
  }
9831
- return callVariadicInstruction(PROPERTY_INTERPOLATE_CONFIG, [literal(name)], interpolationArgs);
9832
- }
9833
- function pureFunction(varOffset, fn, args) {
9834
- return callVariadicInstructionExpr(PURE_FUNCTION_CONFIG, [
9835
- literal(varOffset),
9836
- fn,
9837
- ], args);
9838
- }
9839
- function call(instruction, args) {
9840
- return createStatementOp(importExpr(instruction).callFn(args).toStmt());
10332
+ if (scope.parent !== null) {
10333
+ // Recursively add variables from the parent scope.
10334
+ newOps.push(...generateVariablesInScopeForView(view, scope.parent));
10335
+ }
10336
+ return newOps;
9841
10337
  }
10338
+
9842
10339
  /**
9843
- * `InterpolationConfig` for the `textInterpolate` instruction.
10340
+ * Lifts local reference declarations on element-like structures within each view into an entry in
10341
+ * the `consts` array for the whole component.
9844
10342
  */
9845
- const TEXT_INTERPOLATE_CONFIG = {
9846
- constant: [
9847
- Identifiers.textInterpolate,
9848
- Identifiers.textInterpolate1,
9849
- Identifiers.textInterpolate2,
9850
- Identifiers.textInterpolate3,
9851
- Identifiers.textInterpolate4,
9852
- Identifiers.textInterpolate5,
9853
- Identifiers.textInterpolate6,
9854
- Identifiers.textInterpolate7,
9855
- Identifiers.textInterpolate8,
9856
- ],
9857
- variable: Identifiers.textInterpolateV,
9858
- mapping: n => {
9859
- if (n % 2 === 0) {
9860
- throw new Error(`Expected odd number of arguments`);
9861
- }
9862
- return (n - 1) / 2;
9863
- },
9864
- };
9865
- /**
9866
- * `InterpolationConfig` for the `propertyInterpolate` instruction.
9867
- */
9868
- const PROPERTY_INTERPOLATE_CONFIG = {
9869
- constant: [
9870
- Identifiers.propertyInterpolate,
9871
- Identifiers.propertyInterpolate1,
9872
- Identifiers.propertyInterpolate2,
9873
- Identifiers.propertyInterpolate3,
9874
- Identifiers.propertyInterpolate4,
9875
- Identifiers.propertyInterpolate5,
9876
- Identifiers.propertyInterpolate6,
9877
- Identifiers.propertyInterpolate7,
9878
- Identifiers.propertyInterpolate8,
9879
- ],
9880
- variable: Identifiers.propertyInterpolateV,
9881
- mapping: n => {
9882
- if (n % 2 === 0) {
9883
- throw new Error(`Expected odd number of arguments`);
10343
+ function phaseLocalRefs(cpl) {
10344
+ for (const view of cpl.views.values()) {
10345
+ for (const op of view.create) {
10346
+ switch (op.kind) {
10347
+ case OpKind.ElementStart:
10348
+ case OpKind.Element:
10349
+ case OpKind.Template:
10350
+ if (!Array.isArray(op.localRefs)) {
10351
+ throw new Error(`AssertionError: expected localRefs to be an array still`);
10352
+ }
10353
+ op.numSlotsUsed += op.localRefs.length;
10354
+ if (op.localRefs.length > 0) {
10355
+ const localRefs = serializeLocalRefs(op.localRefs);
10356
+ op.localRefs = cpl.addConst(localRefs);
10357
+ }
10358
+ else {
10359
+ op.localRefs = null;
10360
+ }
10361
+ break;
10362
+ }
9884
10363
  }
9885
- return (n - 1) / 2;
9886
- },
9887
- };
9888
- const PURE_FUNCTION_CONFIG = {
9889
- constant: [
9890
- Identifiers.pureFunction0,
9891
- Identifiers.pureFunction1,
9892
- Identifiers.pureFunction2,
9893
- Identifiers.pureFunction3,
9894
- Identifiers.pureFunction4,
9895
- Identifiers.pureFunction5,
9896
- Identifiers.pureFunction6,
9897
- Identifiers.pureFunction7,
9898
- Identifiers.pureFunction8,
9899
- ],
9900
- variable: Identifiers.pureFunctionV,
9901
- mapping: n => n,
9902
- };
9903
- function callVariadicInstructionExpr(config, baseArgs, interpolationArgs) {
9904
- const n = config.mapping(interpolationArgs.length);
9905
- if (n < config.constant.length) {
9906
- // Constant calling pattern.
9907
- return importExpr(config.constant[n]).callFn([...baseArgs, ...interpolationArgs]);
9908
- }
9909
- else if (config.variable !== null) {
9910
- // Variable calling pattern.
9911
- return importExpr(config.variable).callFn([...baseArgs, literalArr(interpolationArgs)]);
9912
- }
9913
- else {
9914
- throw new Error(`AssertionError: unable to call variadic function`);
9915
10364
  }
9916
10365
  }
9917
- function callVariadicInstruction(config, baseArgs, interpolationArgs) {
9918
- return createStatementOp(callVariadicInstructionExpr(config, baseArgs, interpolationArgs).toStmt());
10366
+ function serializeLocalRefs(refs) {
10367
+ const constRefs = [];
10368
+ for (const ref of refs) {
10369
+ constRefs.push(literal(ref.name), literal(ref.target));
10370
+ }
10371
+ return literalArr(constRefs);
9919
10372
  }
9920
10373
 
9921
10374
  /**
9922
- * Compiles semantic operations across all views and generates output `o.Statement`s with actual
9923
- * runtime calls in their place.
10375
+ * Generate names for functions and variables across all views.
9924
10376
  *
9925
- * Reification replaces semantic operations with selected Ivy instructions and other generated code
9926
- * structures. After reification, the create/update operation lists of all views should only contain
9927
- * `ir.StatementOp`s (which wrap generated `o.Statement`s).
10377
+ * This includes propagating those names into any `ir.ReadVariableExpr`s of those variables, so that
10378
+ * the reads can be emitted correctly.
9928
10379
  */
9929
- function phaseReify(cpl) {
9930
- for (const [_, view] of cpl.views) {
9931
- reifyCreateOperations(view, view.create);
9932
- reifyUpdateOperations(view, view.update);
9933
- }
10380
+ function phaseNaming(cpl) {
10381
+ addNamesToView(cpl.root, cpl.componentName, { index: 0 });
9934
10382
  }
9935
- function reifyCreateOperations(view, ops) {
9936
- for (const op of ops) {
9937
- transformExpressionsInOp(op, reifyIrExpression, VisitorContextFlag.None);
10383
+ function addNamesToView(view, baseName, state) {
10384
+ if (view.fnName === null) {
10385
+ view.fnName = `${baseName}_Template`;
10386
+ }
10387
+ // Keep track of the names we assign to variables in the view. We'll need to propagate these
10388
+ // into reads of those variables afterwards.
10389
+ const varNames = new Map();
10390
+ for (const op of view.ops()) {
9938
10391
  switch (op.kind) {
9939
- case OpKind.Text:
9940
- OpList.replace(op, text(op.slot, op.initialValue));
9941
- break;
9942
- case OpKind.ElementStart:
9943
- OpList.replace(op, elementStart(op.slot, op.tag, op.attributes, op.localRefs));
9944
- break;
9945
- case OpKind.Element:
9946
- OpList.replace(op, element(op.slot, op.tag, op.attributes, op.localRefs));
9947
- break;
9948
- case OpKind.ElementEnd:
9949
- OpList.replace(op, elementEnd());
9950
- break;
9951
- case OpKind.ContainerStart:
9952
- OpList.replace(op, elementContainerStart(op.slot, op.attributes, op.localRefs));
9953
- break;
9954
- case OpKind.Container:
9955
- OpList.replace(op, elementContainer(op.slot, op.attributes, op.localRefs));
10392
+ case OpKind.Listener:
10393
+ if (op.handlerFnName === null) {
10394
+ // TODO(alxhub): convert this temporary name to match how the
10395
+ // `TemplateDefinitionBuilder` names listener functions.
10396
+ if (op.slot === null) {
10397
+ throw new Error(`Expected a slot to be assigned`);
10398
+ }
10399
+ op.handlerFnName = `${view.fnName}_${op.tag}_${op.name}_${op.slot}_listener`;
10400
+ }
9956
10401
  break;
9957
- case OpKind.ContainerEnd:
9958
- OpList.replace(op, elementContainerEnd());
10402
+ case OpKind.Variable:
10403
+ varNames.set(op.xref, getVariableName(op.variable, state));
9959
10404
  break;
9960
10405
  case OpKind.Template:
9961
10406
  const childView = view.tpl.views.get(op.xref);
9962
- OpList.replace(op, template(op.slot, variable(childView.fnName), childView.decls, childView.vars, op.tag, op.attributes));
9963
- break;
9964
- case OpKind.Pipe:
9965
- OpList.replace(op, pipe(op.slot, op.name));
9966
- break;
9967
- case OpKind.Listener:
9968
- const listenerFn = reifyListenerHandler(view, op.handlerFnName, op.handlerOps);
9969
- OpList.replace(op, listener(op.name, listenerFn));
9970
- break;
9971
- case OpKind.Variable:
9972
- if (op.variable.name === null) {
9973
- throw new Error(`AssertionError: unnamed variable ${op.xref}`);
10407
+ if (op.slot === null) {
10408
+ throw new Error(`Expected slot to be assigned`);
9974
10409
  }
9975
- OpList.replace(op, createStatementOp(new DeclareVarStmt(op.variable.name, op.initializer, undefined, StmtModifier.Final)));
9976
- break;
9977
- case OpKind.Statement:
9978
- // Pass statement operations directly through.
10410
+ // TODO: properly escape the tag name.
10411
+ const safeTagName = op.tag.replace('-', '_');
10412
+ addNamesToView(childView, `${baseName}_${safeTagName}_${op.slot}`, state);
9979
10413
  break;
9980
- default:
9981
- throw new Error(`AssertionError: Unsupported reification of create op ${OpKind[op.kind]}`);
9982
10414
  }
9983
10415
  }
10416
+ // Having named all variables declared in the view, now we can push those names into the
10417
+ // `ir.ReadVariableExpr` expressions which represent reads of those variables.
10418
+ for (const op of view.ops()) {
10419
+ visitExpressionsInOp(op, expr => {
10420
+ if (!(expr instanceof ReadVariableExpr) || expr.name !== null) {
10421
+ return;
10422
+ }
10423
+ if (!varNames.has(expr.xref)) {
10424
+ throw new Error(`Variable ${expr.xref} not yet named`);
10425
+ }
10426
+ expr.name = varNames.get(expr.xref);
10427
+ });
10428
+ }
9984
10429
  }
9985
- function reifyUpdateOperations(_view, ops) {
9986
- for (const op of ops) {
9987
- transformExpressionsInOp(op, reifyIrExpression, VisitorContextFlag.None);
9988
- switch (op.kind) {
9989
- case OpKind.Advance:
9990
- OpList.replace(op, advance(op.delta));
9991
- break;
9992
- case OpKind.Property:
9993
- OpList.replace(op, property(op.name, op.expression));
9994
- break;
9995
- case OpKind.InterpolateProperty:
9996
- OpList.replace(op, propertyInterpolate(op.name, op.strings, op.expressions));
9997
- break;
9998
- case OpKind.InterpolateText:
9999
- OpList.replace(op, textInterpolate(op.strings, op.expressions));
10000
- break;
10001
- case OpKind.Variable:
10002
- if (op.variable.name === null) {
10003
- throw new Error(`AssertionError: unnamed variable ${op.xref}`);
10004
- }
10005
- OpList.replace(op, createStatementOp(new DeclareVarStmt(op.variable.name, op.initializer, undefined, StmtModifier.Final)));
10006
- break;
10007
- case OpKind.Statement:
10008
- // Pass statement operations directly through.
10430
+ function getVariableName(variable, state) {
10431
+ if (variable.name === null) {
10432
+ switch (variable.kind) {
10433
+ case SemanticVariableKind.Identifier:
10434
+ variable.name = `${variable.identifier}_${state.index++}`;
10009
10435
  break;
10010
10436
  default:
10011
- throw new Error(`AssertionError: Unsupported reification of update op ${OpKind[op.kind]}`);
10437
+ variable.name = `_r${state.index++}`;
10438
+ break;
10012
10439
  }
10013
10440
  }
10441
+ return variable.name;
10014
10442
  }
10015
- function reifyIrExpression(expr) {
10016
- if (!isIrExpression(expr)) {
10017
- return expr;
10018
- }
10019
- switch (expr.kind) {
10020
- case ExpressionKind.NextContext:
10021
- return nextContext(expr.steps);
10022
- case ExpressionKind.Reference:
10023
- return reference(expr.slot + 1 + expr.offset);
10024
- case ExpressionKind.LexicalRead:
10025
- throw new Error(`AssertionError: unresolved LexicalRead of ${expr.name}`);
10026
- case ExpressionKind.RestoreView:
10027
- if (typeof expr.view === 'number') {
10028
- throw new Error(`AssertionError: unresolved RestoreView`);
10029
- }
10030
- return restoreView(expr.view);
10031
- case ExpressionKind.ResetView:
10032
- return resetView(expr.expr);
10033
- case ExpressionKind.GetCurrentView:
10034
- return getCurrentView();
10035
- case ExpressionKind.ReadVariable:
10036
- if (expr.name === null) {
10037
- throw new Error(`Read of unnamed variable ${expr.xref}`);
10038
- }
10039
- return variable(expr.name);
10040
- case ExpressionKind.PureFunctionExpr:
10041
- if (expr.fn === null) {
10042
- throw new Error(`AssertionError: expected PureFunctions to have been extracted`);
10043
- }
10044
- return pureFunction(expr.varOffset, expr.fn, expr.args);
10045
- case ExpressionKind.PureFunctionParameterExpr:
10046
- throw new Error(`AssertionError: expected PureFunctionParameterExpr to have been extracted`);
10047
- case ExpressionKind.PipeBinding:
10048
- return pipeBind(expr.slot, expr.varOffset, expr.args);
10049
- case ExpressionKind.PipeBindingVariadic:
10050
- return pipeBindV(expr.slot, expr.varOffset, expr.args);
10051
- default:
10052
- throw new Error(`AssertionError: Unsupported reification of ir.Expression kind: ${ExpressionKind[expr.kind]}`);
10053
- }
10054
- }
10443
+
10055
10444
  /**
10056
- * Listeners get turned into a function expression, which may or may not have the `$event`
10057
- * parameter defined.
10058
- */
10059
- function reifyListenerHandler(view, name, handlerOps) {
10060
- const lookForEvent = new LookForEventVisitor();
10061
- // First, reify all instruction calls within `handlerOps`.
10062
- reifyUpdateOperations(view, handlerOps);
10063
- // Next, extract all the `o.Statement`s from the reified operations. We can expect that at this
10064
- // point, all operations have been converted to statements.
10065
- const handlerStmts = [];
10066
- for (const op of handlerOps) {
10067
- if (op.kind !== OpKind.Statement) {
10068
- throw new Error(`AssertionError: expected reified statements, but found op ${OpKind[op.kind]}`);
10445
+ * Merges logically sequential `NextContextExpr` operations.
10446
+ *
10447
+ * `NextContextExpr` can be referenced repeatedly, "popping" the runtime's context stack each time.
10448
+ * When two such expressions appear back-to-back, it's possible to merge them together into a single
10449
+ * `NextContextExpr` that steps multiple contexts. This merging is possible if all conditions are
10450
+ * met:
10451
+ *
10452
+ * * The result of the `NextContextExpr` that's folded into the subsequent one is not stored (that
10453
+ * is, the call is purely side-effectful).
10454
+ * * No operations in between them uses the implicit context.
10455
+ */
10456
+ function phaseMergeNextContext(cpl) {
10457
+ for (const view of cpl.views.values()) {
10458
+ for (const op of view.create) {
10459
+ if (op.kind === OpKind.Listener) {
10460
+ mergeNextContextsInOps(op.handlerOps);
10461
+ }
10069
10462
  }
10070
- handlerStmts.push(op.statement);
10463
+ mergeNextContextsInOps(view.update);
10464
+ }
10465
+ }
10466
+ function mergeNextContextsInOps(ops) {
10467
+ for (const op of ops) {
10468
+ // Look for a candidate operation to maybe merge.
10469
+ if (op.kind !== OpKind.Statement || !(op.statement instanceof ExpressionStatement) ||
10470
+ !(op.statement.expr instanceof NextContextExpr)) {
10471
+ continue;
10472
+ }
10473
+ const mergeSteps = op.statement.expr.steps;
10474
+ // Try to merge this `ir.NextContextExpr`.
10475
+ let tryToMerge = true;
10476
+ for (let candidate = op.next; candidate.kind !== OpKind.ListEnd && tryToMerge; candidate = candidate.next) {
10477
+ visitExpressionsInOp(candidate, (expr, flags) => {
10478
+ if (!isIrExpression(expr)) {
10479
+ return expr;
10480
+ }
10481
+ if (!tryToMerge) {
10482
+ // Either we've already merged, or failed to merge.
10483
+ return;
10484
+ }
10485
+ if (flags & VisitorContextFlag.InChildOperation) {
10486
+ // We cannot merge into child operations.
10487
+ return;
10488
+ }
10489
+ switch (expr.kind) {
10490
+ case ExpressionKind.NextContext:
10491
+ // Merge the previous `ir.NextContextExpr` into this one.
10492
+ expr.steps += mergeSteps;
10493
+ OpList.remove(op);
10494
+ tryToMerge = false;
10495
+ break;
10496
+ case ExpressionKind.GetCurrentView:
10497
+ case ExpressionKind.Reference:
10498
+ // Can't merge past a dependency on the context.
10499
+ tryToMerge = false;
10500
+ break;
10501
+ }
10502
+ });
10503
+ }
10504
+ }
10505
+ }
10506
+
10507
+ const CONTAINER_TAG = 'ng-container';
10508
+ /**
10509
+ * Replace an `Element` or `ElementStart` whose tag is `ng-container` with a specific op.
10510
+ */
10511
+ function phaseNgContainer(cpl) {
10512
+ for (const [_, view] of cpl.views) {
10513
+ const updatedElementXrefs = new Set();
10514
+ for (const op of view.create) {
10515
+ if (op.kind === OpKind.ElementStart && op.tag === CONTAINER_TAG) {
10516
+ // Transmute the `ElementStart` instruction to `ContainerStart`.
10517
+ op.kind = OpKind.ContainerStart;
10518
+ updatedElementXrefs.add(op.xref);
10519
+ }
10520
+ if (op.kind === OpKind.ElementEnd && updatedElementXrefs.has(op.xref)) {
10521
+ // This `ElementEnd` is associated with an `ElementStart` we already transmuted.
10522
+ op.kind = OpKind.ContainerEnd;
10523
+ }
10524
+ }
10525
+ }
10526
+ }
10527
+
10528
+ function phasePipeCreation(cpl) {
10529
+ for (const view of cpl.views.values()) {
10530
+ processPipeBindingsInView(view);
10531
+ }
10532
+ }
10533
+ function processPipeBindingsInView(view) {
10534
+ for (const updateOp of view.update) {
10535
+ visitExpressionsInOp(updateOp, (expr, flags) => {
10536
+ if (!isIrExpression(expr)) {
10537
+ return;
10538
+ }
10539
+ if (expr.kind !== ExpressionKind.PipeBinding) {
10540
+ return;
10541
+ }
10542
+ if (flags & VisitorContextFlag.InChildOperation) {
10543
+ throw new Error(`AssertionError: pipe bindings should not appear in child expressions`);
10544
+ }
10545
+ if (!hasDependsOnSlotContextTrait(updateOp)) {
10546
+ throw new Error(`AssertionError: pipe binding associated with non-slot operation ${OpKind[updateOp.kind]}`);
10547
+ }
10548
+ addPipeToCreationBlock(view, updateOp.target, expr);
10549
+ });
10550
+ }
10551
+ }
10552
+ function addPipeToCreationBlock(view, afterTargetXref, binding) {
10553
+ // Find the appropriate point to insert the Pipe creation operation.
10554
+ // We're looking for `afterTargetXref` (and also want to insert after any other pipe operations
10555
+ // which might be beyond it).
10556
+ for (let op = view.create.head.next; op.kind !== OpKind.ListEnd; op = op.next) {
10557
+ if (!hasConsumesSlotTrait(op)) {
10558
+ continue;
10559
+ }
10560
+ if (op.xref !== afterTargetXref) {
10561
+ continue;
10562
+ }
10563
+ // We've found a tentative insertion point; however, we also want to skip past any _other_ pipe
10564
+ // operations present.
10565
+ while (op.next.kind === OpKind.Pipe) {
10566
+ op = op.next;
10567
+ }
10568
+ const pipe = createPipeOp(binding.target, binding.name);
10569
+ OpList.insertBefore(pipe, op.next);
10570
+ // This completes adding the pipe to the creation block.
10571
+ return;
10572
+ }
10573
+ // At this point, we've failed to add the pipe to the creation block.
10574
+ throw new Error(`AssertionError: unable to find insertion point for pipe ${binding.name}`);
10575
+ }
10576
+
10577
+ function phasePipeVariadic(cpl) {
10578
+ for (const view of cpl.views.values()) {
10579
+ for (const op of view.update) {
10580
+ transformExpressionsInOp(op, expr => {
10581
+ if (!(expr instanceof PipeBindingExpr)) {
10582
+ return expr;
10583
+ }
10584
+ // Pipes are variadic if they have more than 4 arguments.
10585
+ if (expr.args.length <= 4) {
10586
+ return expr;
10587
+ }
10588
+ return new PipeBindingVariadicExpr(expr.target, expr.name, literalArr(expr.args), expr.args.length);
10589
+ }, VisitorContextFlag.None);
10590
+ }
10591
+ }
10592
+ }
10593
+
10594
+ function phasePureFunctionExtraction(cpl) {
10595
+ for (const view of cpl.views.values()) {
10596
+ for (const op of view.ops()) {
10597
+ visitExpressionsInOp(op, expr => {
10598
+ if (!(expr instanceof PureFunctionExpr) || expr.body === null) {
10599
+ return;
10600
+ }
10601
+ const constantDef = new PureFunctionConstant(expr.args.length);
10602
+ expr.fn = cpl.pool.getSharedConstant(constantDef, expr.body);
10603
+ expr.body = null;
10604
+ });
10605
+ }
10606
+ }
10607
+ }
10608
+ class PureFunctionConstant extends GenericKeyFn {
10609
+ constructor(numArgs) {
10610
+ super();
10611
+ this.numArgs = numArgs;
10612
+ }
10613
+ keyOf(expr) {
10614
+ if (expr instanceof PureFunctionParameterExpr) {
10615
+ return `param(${expr.index})`;
10616
+ }
10617
+ else {
10618
+ return super.keyOf(expr);
10619
+ }
10620
+ }
10621
+ toSharedConstantDeclaration(declName, keyExpr) {
10622
+ const fnParams = [];
10623
+ for (let idx = 0; idx < this.numArgs; idx++) {
10624
+ fnParams.push(new FnParam('_p' + idx));
10625
+ }
10626
+ // We will never visit `ir.PureFunctionParameterExpr`s that don't belong to us, because this
10627
+ // transform runs inside another visitor which will visit nested pure functions before this one.
10628
+ const returnExpr = transformExpressionsInExpression(keyExpr, expr => {
10629
+ if (!(expr instanceof PureFunctionParameterExpr)) {
10630
+ return expr;
10631
+ }
10632
+ return variable('_p' + expr.index);
10633
+ }, VisitorContextFlag.None);
10634
+ return new DeclareFunctionStmt(declName, fnParams, [new ReturnStatement(returnExpr)]);
10635
+ }
10636
+ }
10637
+
10638
+ function phasePureLiteralStructures(cpl) {
10639
+ for (const view of cpl.views.values()) {
10640
+ for (const op of view.update) {
10641
+ transformExpressionsInOp(op, (expr, flags) => {
10642
+ if (flags & VisitorContextFlag.InChildOperation) {
10643
+ return expr;
10644
+ }
10645
+ if (expr instanceof LiteralArrayExpr) {
10646
+ return transformLiteralArray(expr);
10647
+ }
10648
+ else if (expr instanceof LiteralMapExpr) {
10649
+ return transformLiteralMap(expr);
10650
+ }
10651
+ return expr;
10652
+ }, VisitorContextFlag.None);
10653
+ }
10654
+ }
10655
+ }
10656
+ function transformLiteralArray(expr) {
10657
+ const derivedEntries = [];
10658
+ const nonConstantArgs = [];
10659
+ for (const entry of expr.entries) {
10660
+ if (entry.isConstant()) {
10661
+ derivedEntries.push(entry);
10662
+ }
10663
+ else {
10664
+ const idx = nonConstantArgs.length;
10665
+ nonConstantArgs.push(entry);
10666
+ derivedEntries.push(new PureFunctionParameterExpr(idx));
10667
+ }
10668
+ }
10669
+ return new PureFunctionExpr(literalArr(derivedEntries), nonConstantArgs);
10670
+ }
10671
+ function transformLiteralMap(expr) {
10672
+ let derivedEntries = [];
10673
+ const nonConstantArgs = [];
10674
+ for (const entry of expr.entries) {
10675
+ if (entry.value.isConstant()) {
10676
+ derivedEntries.push(entry);
10677
+ }
10678
+ else {
10679
+ const idx = nonConstantArgs.length;
10680
+ nonConstantArgs.push(entry.value);
10681
+ derivedEntries.push(new LiteralMapEntry(entry.key, new PureFunctionParameterExpr(idx), entry.quoted));
10682
+ }
10683
+ }
10684
+ return new PureFunctionExpr(literalMap(derivedEntries), nonConstantArgs);
10685
+ }
10686
+
10687
+ // This file contains helpers for generating calls to Ivy instructions. In particular, each
10688
+ // instruction type is represented as a function, which may select a specific instruction variant
10689
+ // depending on the exact arguments.
10690
+ function element(slot, tag, constIndex, localRefIndex) {
10691
+ return elementOrContainerBase(Identifiers.element, slot, tag, constIndex, localRefIndex);
10692
+ }
10693
+ function elementStart(slot, tag, constIndex, localRefIndex) {
10694
+ return elementOrContainerBase(Identifiers.elementStart, slot, tag, constIndex, localRefIndex);
10695
+ }
10696
+ function elementOrContainerBase(instruction, slot, tag, constIndex, localRefIndex) {
10697
+ const args = [literal(slot)];
10698
+ if (tag !== null) {
10699
+ args.push(literal(tag));
10700
+ }
10701
+ if (localRefIndex !== null) {
10702
+ args.push(literal(constIndex), // might be null, but that's okay.
10703
+ literal(localRefIndex));
10704
+ }
10705
+ else if (constIndex !== null) {
10706
+ args.push(literal(constIndex));
10707
+ }
10708
+ return call(instruction, args);
10709
+ }
10710
+ function elementEnd() {
10711
+ return call(Identifiers.elementEnd, []);
10712
+ }
10713
+ function elementContainerStart(slot, constIndex, localRefIndex) {
10714
+ return elementOrContainerBase(Identifiers.elementContainerStart, slot, /* tag */ null, constIndex, localRefIndex);
10715
+ }
10716
+ function elementContainer(slot, constIndex, localRefIndex) {
10717
+ return elementOrContainerBase(Identifiers.elementContainer, slot, /* tag */ null, constIndex, localRefIndex);
10718
+ }
10719
+ function elementContainerEnd() {
10720
+ return call(Identifiers.elementContainerEnd, []);
10721
+ }
10722
+ function template(slot, templateFnRef, decls, vars, tag, constIndex) {
10723
+ return call(Identifiers.templateCreate, [
10724
+ literal(slot),
10725
+ templateFnRef,
10726
+ literal(decls),
10727
+ literal(vars),
10728
+ literal(tag),
10729
+ literal(constIndex),
10730
+ ]);
10731
+ }
10732
+ function listener(name, handlerFn) {
10733
+ return call(Identifiers.listener, [
10734
+ literal(name),
10735
+ handlerFn,
10736
+ ]);
10737
+ }
10738
+ function pipe(slot, name) {
10739
+ return call(Identifiers.pipe, [
10740
+ literal(slot),
10741
+ literal(name),
10742
+ ]);
10743
+ }
10744
+ function advance(delta) {
10745
+ return call(Identifiers.advance, [
10746
+ literal(delta),
10747
+ ]);
10748
+ }
10749
+ function reference(slot) {
10750
+ return importExpr(Identifiers.reference).callFn([
10751
+ literal(slot),
10752
+ ]);
10753
+ }
10754
+ function nextContext(steps) {
10755
+ return importExpr(Identifiers.nextContext).callFn(steps === 1 ? [] : [literal(steps)]);
10756
+ }
10757
+ function getCurrentView() {
10758
+ return importExpr(Identifiers.getCurrentView).callFn([]);
10759
+ }
10760
+ function restoreView(savedView) {
10761
+ return importExpr(Identifiers.restoreView).callFn([
10762
+ savedView,
10763
+ ]);
10764
+ }
10765
+ function resetView(returnValue) {
10766
+ return importExpr(Identifiers.resetView).callFn([
10767
+ returnValue,
10768
+ ]);
10769
+ }
10770
+ function text(slot, initialValue) {
10771
+ const args = [literal(slot)];
10772
+ if (initialValue !== '') {
10773
+ args.push(literal(initialValue));
10071
10774
  }
10072
- // Scan the statement list for usages of `$event`. If referenced, we need to generate it as a
10073
- // parameter.
10074
- lookForEvent.visitAllStatements(handlerStmts, null);
10075
- const params = [];
10076
- if (lookForEvent.seenEventRead) {
10077
- // We need the `$event` parameter.
10078
- params.push(new FnParam('$event'));
10775
+ return call(Identifiers.text, args);
10776
+ }
10777
+ function property(name, expression) {
10778
+ return call(Identifiers.property, [
10779
+ literal(name),
10780
+ expression,
10781
+ ]);
10782
+ }
10783
+ const PIPE_BINDINGS = [
10784
+ Identifiers.pipeBind1,
10785
+ Identifiers.pipeBind2,
10786
+ Identifiers.pipeBind3,
10787
+ Identifiers.pipeBind4,
10788
+ ];
10789
+ function pipeBind(slot, varOffset, args) {
10790
+ if (args.length < 1 || args.length > PIPE_BINDINGS.length) {
10791
+ throw new Error(`pipeBind() argument count out of bounds`);
10079
10792
  }
10080
- return fn(params, handlerStmts, undefined, undefined, name);
10793
+ const instruction = PIPE_BINDINGS[args.length - 1];
10794
+ return importExpr(instruction).callFn([
10795
+ literal(slot),
10796
+ literal(varOffset),
10797
+ ...args,
10798
+ ]);
10081
10799
  }
10082
- /**
10083
- * Visitor which scans for reads of the `$event` special variable.
10084
- */
10085
- class LookForEventVisitor extends RecursiveAstVisitor$1 {
10086
- constructor() {
10087
- super(...arguments);
10088
- this.seenEventRead = false;
10800
+ function pipeBindV(slot, varOffset, args) {
10801
+ return importExpr(Identifiers.pipeBindV).callFn([
10802
+ literal(slot),
10803
+ literal(varOffset),
10804
+ args,
10805
+ ]);
10806
+ }
10807
+ function textInterpolate(strings, expressions) {
10808
+ if (strings.length < 1 || expressions.length !== strings.length - 1) {
10809
+ throw new Error(`AssertionError: expected specific shape of args for strings/expressions in interpolation`);
10089
10810
  }
10090
- visitReadVarExpr(ast, context) {
10091
- if (ast.name === '$event') {
10092
- this.seenEventRead = true;
10811
+ const interpolationArgs = [];
10812
+ if (expressions.length === 1 && strings[0] === '' && strings[1] === '') {
10813
+ interpolationArgs.push(expressions[0]);
10814
+ }
10815
+ else {
10816
+ let idx;
10817
+ for (idx = 0; idx < expressions.length; idx++) {
10818
+ interpolationArgs.push(literal(strings[idx]), expressions[idx]);
10093
10819
  }
10820
+ // idx points at the last string.
10821
+ interpolationArgs.push(literal(strings[idx]));
10094
10822
  }
10823
+ return callVariadicInstruction(TEXT_INTERPOLATE_CONFIG, [], interpolationArgs);
10095
10824
  }
10096
-
10097
- /**
10098
- * Assign data slots for all operations which implement `ConsumesSlotOpTrait`, and propagate the
10099
- * assigned data slots of those operations to any expressions which reference them via
10100
- * `UsesSlotIndexTrait`.
10101
- *
10102
- * This phase is also responsible for counting the number of slots used for each view (its `decls`)
10103
- * and propagating that number into the `Template` operations which declare embedded views.
10104
- */
10105
- function phaseSlotAllocation(cpl) {
10106
- // Map of all declarations in all views within the component which require an assigned slot index.
10107
- // This map needs to be global (across all views within the component) since it's possible to
10108
- // reference a slot from one view from an expression within another (e.g. local references work
10109
- // this way).
10110
- const slotMap = new Map();
10111
- // Process all views in the component and assign slot indexes.
10112
- for (const [_, view] of cpl.views) {
10113
- // Slot indices start at 0 for each view (and are not unique between views).
10114
- let slotCount = 0;
10115
- for (const op of view.create) {
10116
- // Only consider declarations which consume data slots.
10117
- if (!hasConsumesSlotTrait(op)) {
10118
- continue;
10119
- }
10120
- // Assign slots to this declaration starting at the current `slotCount`.
10121
- op.slot = slotCount;
10122
- // And track its assigned slot in the `slotMap`.
10123
- slotMap.set(op.xref, op.slot);
10124
- // Each declaration may use more than 1 slot, so increment `slotCount` to reserve the number
10125
- // of slots required.
10126
- slotCount += op.numSlotsUsed;
10127
- }
10128
- // Record the total number of slots used on the view itself. This will later be propagated into
10129
- // `ir.TemplateOp`s which declare those views (except for the root view).
10130
- view.decls = slotCount;
10825
+ function propertyInterpolate(name, strings, expressions) {
10826
+ if (strings.length < 1 || expressions.length !== strings.length - 1) {
10827
+ throw new Error(`AssertionError: expected specific shape of args for strings/expressions in interpolation`);
10131
10828
  }
10132
- // After slot assignment, `slotMap` now contains slot assignments for every declaration in the
10133
- // whole template, across all views. Next, look for expressions which implement
10134
- // `UsesSlotIndexExprTrait` and propagate the assigned slot indexes into them.
10135
- // Additionally, this second scan allows us to find `ir.TemplateOp`s which declare views and
10136
- // propagate the number of slots used for each view into the operation which declares it.
10137
- for (const [_, view] of cpl.views) {
10138
- for (const op of view.ops()) {
10139
- if (op.kind === OpKind.Template) {
10140
- // Record the number of slots used by the view this `ir.TemplateOp` declares in the
10141
- // operation itself, so it can be emitted later.
10142
- const childView = cpl.views.get(op.xref);
10143
- op.decls = childView.decls;
10144
- }
10145
- if (hasUsesSlotIndexTrait(op) && op.slot === null) {
10146
- if (!slotMap.has(op.target)) {
10147
- // We do expect to find a slot allocated for everything which might be referenced.
10148
- throw new Error(`AssertionError: no slot allocated for ${OpKind[op.kind]} target ${op.target}`);
10149
- }
10150
- op.slot = slotMap.get(op.target);
10151
- }
10152
- // Process all `ir.Expression`s within this view, and look for `usesSlotIndexExprTrait`.
10153
- visitExpressionsInOp(op, expr => {
10154
- if (!isIrExpression(expr)) {
10155
- return;
10156
- }
10157
- if (!hasUsesSlotIndexTrait(expr) || expr.slot !== null) {
10158
- return;
10159
- }
10160
- // The `UsesSlotIndexExprTrait` indicates that this expression references something declared
10161
- // in this component template by its slot index. Use the `target` `ir.XrefId` to find the
10162
- // allocated slot for that declaration in `slotMap`.
10163
- if (!slotMap.has(expr.target)) {
10164
- // We do expect to find a slot allocated for everything which might be referenced.
10165
- throw new Error(`AssertionError: no slot allocated for ${expr.constructor.name} target ${expr.target}`);
10166
- }
10167
- // Record the allocated slot on the expression.
10168
- expr.slot = slotMap.get(expr.target);
10169
- });
10829
+ const interpolationArgs = [];
10830
+ if (expressions.length === 1 && strings[0] === '' && strings[1] === '') {
10831
+ interpolationArgs.push(expressions[0]);
10832
+ }
10833
+ else {
10834
+ let idx;
10835
+ for (idx = 0; idx < expressions.length; idx++) {
10836
+ interpolationArgs.push(literal(strings[idx]), expressions[idx]);
10170
10837
  }
10838
+ // idx points at the last string.
10839
+ interpolationArgs.push(literal(strings[idx]));
10171
10840
  }
10841
+ return callVariadicInstruction(PROPERTY_INTERPOLATE_CONFIG, [literal(name)], interpolationArgs);
10842
+ }
10843
+ function pureFunction(varOffset, fn, args) {
10844
+ return callVariadicInstructionExpr(PURE_FUNCTION_CONFIG, [
10845
+ literal(varOffset),
10846
+ fn,
10847
+ ], args);
10848
+ }
10849
+ function call(instruction, args) {
10850
+ return createStatementOp(importExpr(instruction).callFn(args).toStmt());
10172
10851
  }
10173
-
10174
10852
  /**
10175
- * Counts the number of variable slots used within each view, and stores that on the view itself, as
10176
- * well as propagates it to the `ir.TemplateOp` for embedded views.
10853
+ * `InterpolationConfig` for the `textInterpolate` instruction.
10177
10854
  */
10178
- function phaseVarCounting(cpl) {
10179
- // First, count the vars used in each view, and update the view-level counter.
10180
- for (const [_, view] of cpl.views) {
10181
- let varCount = 0;
10182
- for (const op of view.ops()) {
10183
- if (hasConsumesVarsTrait(op)) {
10184
- varCount += varsUsedByOp(op);
10185
- }
10186
- visitExpressionsInOp(op, expr => {
10187
- if (!isIrExpression(expr)) {
10188
- return;
10189
- }
10190
- // Some expressions require knowledge of the number of variable slots consumed.
10191
- if (hasUsesVarOffsetTrait(expr)) {
10192
- expr.varOffset = varCount;
10193
- }
10194
- if (hasConsumesVarsTrait(expr)) {
10195
- varCount += varsUsedByIrExpression(expr);
10196
- }
10197
- });
10855
+ const TEXT_INTERPOLATE_CONFIG = {
10856
+ constant: [
10857
+ Identifiers.textInterpolate,
10858
+ Identifiers.textInterpolate1,
10859
+ Identifiers.textInterpolate2,
10860
+ Identifiers.textInterpolate3,
10861
+ Identifiers.textInterpolate4,
10862
+ Identifiers.textInterpolate5,
10863
+ Identifiers.textInterpolate6,
10864
+ Identifiers.textInterpolate7,
10865
+ Identifiers.textInterpolate8,
10866
+ ],
10867
+ variable: Identifiers.textInterpolateV,
10868
+ mapping: n => {
10869
+ if (n % 2 === 0) {
10870
+ throw new Error(`Expected odd number of arguments`);
10198
10871
  }
10199
- view.vars = varCount;
10200
- }
10201
- // Add var counts for each view to the `ir.TemplateOp` which declares that view (if the view is an
10202
- // embedded view).
10203
- for (const [_, view] of cpl.views) {
10204
- for (const op of view.create) {
10205
- if (op.kind !== OpKind.Template) {
10206
- continue;
10207
- }
10208
- const childView = cpl.views.get(op.xref);
10209
- op.vars = childView.vars;
10872
+ return (n - 1) / 2;
10873
+ },
10874
+ };
10875
+ /**
10876
+ * `InterpolationConfig` for the `propertyInterpolate` instruction.
10877
+ */
10878
+ const PROPERTY_INTERPOLATE_CONFIG = {
10879
+ constant: [
10880
+ Identifiers.propertyInterpolate,
10881
+ Identifiers.propertyInterpolate1,
10882
+ Identifiers.propertyInterpolate2,
10883
+ Identifiers.propertyInterpolate3,
10884
+ Identifiers.propertyInterpolate4,
10885
+ Identifiers.propertyInterpolate5,
10886
+ Identifiers.propertyInterpolate6,
10887
+ Identifiers.propertyInterpolate7,
10888
+ Identifiers.propertyInterpolate8,
10889
+ ],
10890
+ variable: Identifiers.propertyInterpolateV,
10891
+ mapping: n => {
10892
+ if (n % 2 === 0) {
10893
+ throw new Error(`Expected odd number of arguments`);
10210
10894
  }
10895
+ return (n - 1) / 2;
10896
+ },
10897
+ };
10898
+ const PURE_FUNCTION_CONFIG = {
10899
+ constant: [
10900
+ Identifiers.pureFunction0,
10901
+ Identifiers.pureFunction1,
10902
+ Identifiers.pureFunction2,
10903
+ Identifiers.pureFunction3,
10904
+ Identifiers.pureFunction4,
10905
+ Identifiers.pureFunction5,
10906
+ Identifiers.pureFunction6,
10907
+ Identifiers.pureFunction7,
10908
+ Identifiers.pureFunction8,
10909
+ ],
10910
+ variable: Identifiers.pureFunctionV,
10911
+ mapping: n => n,
10912
+ };
10913
+ function callVariadicInstructionExpr(config, baseArgs, interpolationArgs) {
10914
+ const n = config.mapping(interpolationArgs.length);
10915
+ if (n < config.constant.length) {
10916
+ // Constant calling pattern.
10917
+ return importExpr(config.constant[n]).callFn([...baseArgs, ...interpolationArgs]);
10211
10918
  }
10212
- }
10213
- /**
10214
- * Different operations that implement `ir.UsesVarsTrait` use different numbers of variables, so
10215
- * count the variables used by any particular `op`.
10216
- */
10217
- function varsUsedByOp(op) {
10218
- switch (op.kind) {
10219
- case OpKind.Property:
10220
- // Property bindings use 1 variable slot.
10221
- return 1;
10222
- case OpKind.InterpolateText:
10223
- // `ir.InterpolateTextOp`s use a variable slot for each dynamic expression.
10224
- return op.expressions.length;
10225
- case OpKind.InterpolateProperty:
10226
- // `ir.InterpolatePropertyOp`s use a variable slot for each dynamic expression, plus one for
10227
- // the result.
10228
- return 1 + op.expressions.length;
10229
- default:
10230
- throw new Error(`Unhandled op: ${OpKind[op.kind]}`);
10919
+ else if (config.variable !== null) {
10920
+ // Variable calling pattern.
10921
+ return importExpr(config.variable).callFn([...baseArgs, literalArr(interpolationArgs)]);
10231
10922
  }
10232
- }
10233
- function varsUsedByIrExpression(expr) {
10234
- switch (expr.kind) {
10235
- case ExpressionKind.PureFunctionExpr:
10236
- return 1 + expr.args.length;
10237
- case ExpressionKind.PipeBinding:
10238
- return 1 + expr.args.length;
10239
- case ExpressionKind.PipeBindingVariadic:
10240
- return 1 + expr.numArgs;
10241
- default:
10242
- throw new Error(`AssertionError: unhandled ConsumesVarsTrait expression ${expr.constructor.name}`);
10923
+ else {
10924
+ throw new Error(`AssertionError: unable to call variadic function`);
10243
10925
  }
10244
10926
  }
10927
+ function callVariadicInstruction(config, baseArgs, interpolationArgs) {
10928
+ return createStatementOp(callVariadicInstructionExpr(config, baseArgs, interpolationArgs).toStmt());
10929
+ }
10245
10930
 
10246
10931
  /**
10247
- * Generate names for functions and variables across all views.
10932
+ * Compiles semantic operations across all views and generates output `o.Statement`s with actual
10933
+ * runtime calls in their place.
10248
10934
  *
10249
- * This includes propagating those names into any `ir.ReadVariableExpr`s of those variables, so that
10250
- * the reads can be emitted correctly.
10935
+ * Reification replaces semantic operations with selected Ivy instructions and other generated code
10936
+ * structures. After reification, the create/update operation lists of all views should only contain
10937
+ * `ir.StatementOp`s (which wrap generated `o.Statement`s).
10251
10938
  */
10252
- function phaseNaming(cpl) {
10253
- addNamesToView(cpl.root, cpl.componentName, { index: 0 });
10254
- }
10255
- function addNamesToView(view, baseName, state) {
10256
- if (view.fnName === null) {
10257
- view.fnName = `${baseName}_Template`;
10939
+ function phaseReify(cpl) {
10940
+ for (const [_, view] of cpl.views) {
10941
+ reifyCreateOperations(view, view.create);
10942
+ reifyUpdateOperations(view, view.update);
10258
10943
  }
10259
- // Keep track of the names we assign to variables in the view. We'll need to propagate these
10260
- // into reads of those variables afterwards.
10261
- const varNames = new Map();
10262
- for (const op of view.ops()) {
10944
+ }
10945
+ function reifyCreateOperations(view, ops) {
10946
+ for (const op of ops) {
10947
+ transformExpressionsInOp(op, reifyIrExpression, VisitorContextFlag.None);
10263
10948
  switch (op.kind) {
10264
- case OpKind.Listener:
10265
- if (op.handlerFnName === null) {
10266
- // TODO(alxhub): convert this temporary name to match how the
10267
- // `TemplateDefinitionBuilder` names listener functions.
10268
- if (op.slot === null) {
10269
- throw new Error(`Expected a slot to be assigned`);
10270
- }
10271
- op.handlerFnName = `${view.fnName}_${op.tag}_${op.name}_${op.slot}_listener`;
10272
- }
10949
+ case OpKind.Text:
10950
+ OpList.replace(op, text(op.slot, op.initialValue));
10273
10951
  break;
10274
- case OpKind.Variable:
10275
- varNames.set(op.xref, getVariableName(op.variable, state));
10952
+ case OpKind.ElementStart:
10953
+ OpList.replace(op, elementStart(op.slot, op.tag, op.attributes, op.localRefs));
10954
+ break;
10955
+ case OpKind.Element:
10956
+ OpList.replace(op, element(op.slot, op.tag, op.attributes, op.localRefs));
10957
+ break;
10958
+ case OpKind.ElementEnd:
10959
+ OpList.replace(op, elementEnd());
10960
+ break;
10961
+ case OpKind.ContainerStart:
10962
+ OpList.replace(op, elementContainerStart(op.slot, op.attributes, op.localRefs));
10963
+ break;
10964
+ case OpKind.Container:
10965
+ OpList.replace(op, elementContainer(op.slot, op.attributes, op.localRefs));
10966
+ break;
10967
+ case OpKind.ContainerEnd:
10968
+ OpList.replace(op, elementContainerEnd());
10276
10969
  break;
10277
10970
  case OpKind.Template:
10278
10971
  const childView = view.tpl.views.get(op.xref);
10279
- if (op.slot === null) {
10280
- throw new Error(`Expected slot to be assigned`);
10972
+ OpList.replace(op, template(op.slot, variable(childView.fnName), childView.decls, childView.vars, op.tag, op.attributes));
10973
+ break;
10974
+ case OpKind.Pipe:
10975
+ OpList.replace(op, pipe(op.slot, op.name));
10976
+ break;
10977
+ case OpKind.Listener:
10978
+ const listenerFn = reifyListenerHandler(view, op.handlerFnName, op.handlerOps);
10979
+ OpList.replace(op, listener(op.name, listenerFn));
10980
+ break;
10981
+ case OpKind.Variable:
10982
+ if (op.variable.name === null) {
10983
+ throw new Error(`AssertionError: unnamed variable ${op.xref}`);
10281
10984
  }
10282
- // TODO: properly escape the tag name.
10283
- const safeTagName = op.tag.replace('-', '_');
10284
- addNamesToView(childView, `${baseName}_${safeTagName}_${op.slot}`, state);
10985
+ OpList.replace(op, createStatementOp(new DeclareVarStmt(op.variable.name, op.initializer, undefined, StmtModifier.Final)));
10986
+ break;
10987
+ case OpKind.Statement:
10988
+ // Pass statement operations directly through.
10285
10989
  break;
10990
+ default:
10991
+ throw new Error(`AssertionError: Unsupported reification of create op ${OpKind[op.kind]}`);
10286
10992
  }
10287
10993
  }
10288
- // Having named all variables declared in the view, now we can push those names into the
10289
- // `ir.ReadVariableExpr` expressions which represent reads of those variables.
10290
- for (const op of view.ops()) {
10291
- visitExpressionsInOp(op, expr => {
10292
- if (!(expr instanceof ReadVariableExpr) || expr.name !== null) {
10293
- return;
10294
- }
10295
- if (!varNames.has(expr.xref)) {
10296
- throw new Error(`Variable ${expr.xref} not yet named`);
10297
- }
10298
- expr.name = varNames.get(expr.xref);
10299
- });
10300
- }
10301
10994
  }
10302
- function getVariableName(variable, state) {
10303
- if (variable.name === null) {
10304
- switch (variable.kind) {
10305
- case SemanticVariableKind.Identifier:
10306
- variable.name = `${variable.identifier}_${state.index++}`;
10995
+ function reifyUpdateOperations(_view, ops) {
10996
+ for (const op of ops) {
10997
+ transformExpressionsInOp(op, reifyIrExpression, VisitorContextFlag.None);
10998
+ switch (op.kind) {
10999
+ case OpKind.Advance:
11000
+ OpList.replace(op, advance(op.delta));
10307
11001
  break;
10308
- default:
10309
- variable.name = `_r${state.index++}`;
11002
+ case OpKind.Property:
11003
+ OpList.replace(op, property(op.name, op.expression));
11004
+ break;
11005
+ case OpKind.InterpolateProperty:
11006
+ OpList.replace(op, propertyInterpolate(op.name, op.strings, op.expressions));
11007
+ break;
11008
+ case OpKind.InterpolateText:
11009
+ OpList.replace(op, textInterpolate(op.strings, op.expressions));
11010
+ break;
11011
+ case OpKind.Variable:
11012
+ if (op.variable.name === null) {
11013
+ throw new Error(`AssertionError: unnamed variable ${op.xref}`);
11014
+ }
11015
+ OpList.replace(op, createStatementOp(new DeclareVarStmt(op.variable.name, op.initializer, undefined, StmtModifier.Final)));
11016
+ break;
11017
+ case OpKind.Statement:
11018
+ // Pass statement operations directly through.
10310
11019
  break;
11020
+ default:
11021
+ throw new Error(`AssertionError: Unsupported reification of update op ${OpKind[op.kind]}`);
10311
11022
  }
10312
11023
  }
10313
- return variable.name;
10314
11024
  }
10315
-
10316
- /**
10317
- * Lifts local reference declarations on element-like structures within each view into an entry in
10318
- * the `consts` array for the whole component.
10319
- */
10320
- function phaseLocalRefs(cpl) {
10321
- for (const view of cpl.views.values()) {
10322
- for (const op of view.create) {
10323
- switch (op.kind) {
10324
- case OpKind.ElementStart:
10325
- case OpKind.Element:
10326
- case OpKind.Template:
10327
- if (!Array.isArray(op.localRefs)) {
10328
- throw new Error(`AssertionError: expected localRefs to be an array still`);
10329
- }
10330
- op.numSlotsUsed += op.localRefs.length;
10331
- if (op.localRefs.length > 0) {
10332
- const localRefs = serializeLocalRefs(op.localRefs);
10333
- op.localRefs = cpl.addConst(localRefs);
10334
- }
10335
- else {
10336
- op.localRefs = null;
10337
- }
10338
- break;
10339
- }
10340
- }
11025
+ function reifyIrExpression(expr) {
11026
+ if (!isIrExpression(expr)) {
11027
+ return expr;
10341
11028
  }
10342
- }
10343
- function serializeLocalRefs(refs) {
10344
- const constRefs = [];
10345
- for (const ref of refs) {
10346
- constRefs.push(literal(ref.name), literal(ref.target));
11029
+ switch (expr.kind) {
11030
+ case ExpressionKind.NextContext:
11031
+ return nextContext(expr.steps);
11032
+ case ExpressionKind.Reference:
11033
+ return reference(expr.slot + 1 + expr.offset);
11034
+ case ExpressionKind.LexicalRead:
11035
+ throw new Error(`AssertionError: unresolved LexicalRead of ${expr.name}`);
11036
+ case ExpressionKind.RestoreView:
11037
+ if (typeof expr.view === 'number') {
11038
+ throw new Error(`AssertionError: unresolved RestoreView`);
11039
+ }
11040
+ return restoreView(expr.view);
11041
+ case ExpressionKind.ResetView:
11042
+ return resetView(expr.expr);
11043
+ case ExpressionKind.GetCurrentView:
11044
+ return getCurrentView();
11045
+ case ExpressionKind.ReadVariable:
11046
+ if (expr.name === null) {
11047
+ throw new Error(`Read of unnamed variable ${expr.xref}`);
11048
+ }
11049
+ return variable(expr.name);
11050
+ case ExpressionKind.PureFunctionExpr:
11051
+ if (expr.fn === null) {
11052
+ throw new Error(`AssertionError: expected PureFunctions to have been extracted`);
11053
+ }
11054
+ return pureFunction(expr.varOffset, expr.fn, expr.args);
11055
+ case ExpressionKind.PureFunctionParameterExpr:
11056
+ throw new Error(`AssertionError: expected PureFunctionParameterExpr to have been extracted`);
11057
+ case ExpressionKind.PipeBinding:
11058
+ return pipeBind(expr.slot, expr.varOffset, expr.args);
11059
+ case ExpressionKind.PipeBindingVariadic:
11060
+ return pipeBindV(expr.slot, expr.varOffset, expr.args);
11061
+ default:
11062
+ throw new Error(`AssertionError: Unsupported reification of ir.Expression kind: ${ExpressionKind[expr.kind]}`);
10347
11063
  }
10348
- return literalArr(constRefs);
10349
11064
  }
10350
-
10351
11065
  /**
10352
- * Generate a preamble sequence for each view creation block and listener function which declares
10353
- * any variables that be referenced in other operations in the block.
10354
- *
10355
- * Variables generated include:
10356
- * * a saved view context to be used to restore the current view in event listeners.
10357
- * * the context of the restored view within event listener handlers.
10358
- * * context variables from the current view as well as all parent views (including the root
10359
- * context if needed).
10360
- * * local references from elements within the current view and any lexical parents.
10361
- *
10362
- * Variables are generated here unconditionally, and may optimized away in future operations if it
10363
- * turns out their values (and any side effects) are unused.
11066
+ * Listeners get turned into a function expression, which may or may not have the `$event`
11067
+ * parameter defined.
10364
11068
  */
10365
- function phaseGenerateVariables(cpl) {
10366
- recursivelyProcessView(cpl.root, /* there is no parent scope for the root view */ null);
11069
+ function reifyListenerHandler(view, name, handlerOps) {
11070
+ const lookForEvent = new LookForEventVisitor();
11071
+ // First, reify all instruction calls within `handlerOps`.
11072
+ reifyUpdateOperations(view, handlerOps);
11073
+ // Next, extract all the `o.Statement`s from the reified operations. We can expect that at this
11074
+ // point, all operations have been converted to statements.
11075
+ const handlerStmts = [];
11076
+ for (const op of handlerOps) {
11077
+ if (op.kind !== OpKind.Statement) {
11078
+ throw new Error(`AssertionError: expected reified statements, but found op ${OpKind[op.kind]}`);
11079
+ }
11080
+ handlerStmts.push(op.statement);
11081
+ }
11082
+ // Scan the statement list for usages of `$event`. If referenced, we need to generate it as a
11083
+ // parameter.
11084
+ lookForEvent.visitAllStatements(handlerStmts, null);
11085
+ const params = [];
11086
+ if (lookForEvent.seenEventRead) {
11087
+ // We need the `$event` parameter.
11088
+ params.push(new FnParam('$event'));
11089
+ }
11090
+ return fn(params, handlerStmts, undefined, undefined, name);
10367
11091
  }
10368
11092
  /**
10369
- * Process the given `ViewCompilation` and generate preambles for it and any listeners that it
10370
- * declares.
10371
- *
10372
- * @param `parentScope` a scope extracted from the parent view which captures any variables which
10373
- * should be inherited by this view. `null` if the current view is the root view.
11093
+ * Visitor which scans for reads of the `$event` special variable.
10374
11094
  */
10375
- function recursivelyProcessView(view, parentScope) {
10376
- // Extract a `Scope` from this view.
10377
- const scope = getScopeForView(view, parentScope);
10378
- // Embedded views require an operation to save/restore the view context.
10379
- if (view.parent !== null) {
10380
- // Start the view creation block with an operation to save the current view context. This may be
10381
- // used to restore the view context in any listeners that may be present.
11095
+ class LookForEventVisitor extends RecursiveAstVisitor$1 {
11096
+ constructor() {
11097
+ super(...arguments);
11098
+ this.seenEventRead = false;
10382
11099
  }
10383
- for (const op of view.create) {
10384
- switch (op.kind) {
10385
- case OpKind.Template:
10386
- // Descend into child embedded views.
10387
- recursivelyProcessView(view.tpl.views.get(op.xref), scope);
10388
- break;
10389
- case OpKind.Listener:
10390
- // Prepend variables to listener handler functions.
10391
- op.handlerOps.prepend(generateVariablesInScopeForView(view, scope));
10392
- break;
11100
+ visitReadVarExpr(ast, context) {
11101
+ if (ast.name === '$event') {
11102
+ this.seenEventRead = true;
10393
11103
  }
10394
11104
  }
10395
- // Prepend the declarations for all available variables in scope to the `update` block.
10396
- const preambleOps = generateVariablesInScopeForView(view, scope);
10397
- view.update.prepend(preambleOps);
10398
11105
  }
11106
+
10399
11107
  /**
10400
- * Process a view and generate a `Scope` representing the variables available for reference within
10401
- * that view.
11108
+ * Resolves `ir.ContextExpr` expressions (which represent embedded view or component contexts) to
11109
+ * either the `ctx` parameter to component functions (for the current view context) or to variables
11110
+ * that store those contexts (for contexts accessed via the `nextContext()` instruction).
10402
11111
  */
10403
- function getScopeForView(view, parent) {
10404
- const scope = {
10405
- view: view.xref,
10406
- viewContextVariable: {
10407
- kind: SemanticVariableKind.Context,
10408
- name: null,
10409
- view: view.xref,
10410
- },
10411
- contextVariables: new Map(),
10412
- references: [],
10413
- parent,
10414
- };
10415
- for (const identifier of view.contextVariables.keys()) {
10416
- scope.contextVariables.set(identifier, {
10417
- kind: SemanticVariableKind.Identifier,
10418
- name: null,
10419
- identifier,
10420
- });
11112
+ function phaseResolveContexts(cpl) {
11113
+ for (const view of cpl.views.values()) {
11114
+ processLexicalScope$1(view, view.create);
11115
+ processLexicalScope$1(view, view.update);
10421
11116
  }
10422
- for (const op of view.create) {
11117
+ }
11118
+ function processLexicalScope$1(view, ops) {
11119
+ // Track the expressions used to access all available contexts within the current view, by the
11120
+ // view `ir.XrefId`.
11121
+ const scope = new Map();
11122
+ // The current view's context is accessible via the `ctx` parameter.
11123
+ scope.set(view.xref, variable('ctx'));
11124
+ for (const op of ops) {
10423
11125
  switch (op.kind) {
10424
- case OpKind.Element:
10425
- case OpKind.ElementStart:
10426
- case OpKind.Template:
10427
- if (!Array.isArray(op.localRefs)) {
10428
- throw new Error(`AssertionError: expected localRefs to be an array`);
10429
- }
10430
- // Record available local references from this element.
10431
- for (let offset = 0; offset < op.localRefs.length; offset++) {
10432
- scope.references.push({
10433
- name: op.localRefs[offset].name,
10434
- targetId: op.xref,
10435
- offset,
10436
- variable: {
10437
- kind: SemanticVariableKind.Identifier,
10438
- name: null,
10439
- identifier: op.localRefs[offset].name,
10440
- },
10441
- });
11126
+ case OpKind.Variable:
11127
+ switch (op.variable.kind) {
11128
+ case SemanticVariableKind.Context:
11129
+ scope.set(op.variable.view, new ReadVariableExpr(op.xref));
11130
+ break;
10442
11131
  }
10443
11132
  break;
11133
+ case OpKind.Listener:
11134
+ processLexicalScope$1(view, op.handlerOps);
11135
+ break;
10444
11136
  }
10445
11137
  }
10446
- return scope;
10447
- }
10448
- /**
10449
- * Generate declarations for all variables that are in scope for a given view.
10450
- *
10451
- * This is a recursive process, as views inherit variables available from their parent view, which
10452
- * itself may have inherited variables, etc.
10453
- */
10454
- function generateVariablesInScopeForView(view, scope) {
10455
- const newOps = [];
10456
- if (scope.view !== view.xref) {
10457
- // Before generating variables for a parent view, we need to switch to the context of the parent
10458
- // view with a `nextContext` expression. This context switching operation itself declares a
10459
- // variable, because the context of the view may be referenced directly.
10460
- newOps.push(createVariableOp(view.tpl.allocateXrefId(), scope.viewContextVariable, new NextContextExpr()));
10461
- }
10462
- // Add variables for all context variables available in this scope's view.
10463
- for (const [name, value] of view.tpl.views.get(scope.view).contextVariables) {
10464
- newOps.push(createVariableOp(view.tpl.allocateXrefId(), scope.contextVariables.get(name), new ReadPropExpr(new ContextExpr(scope.view), value)));
10465
- }
10466
- // Add variables for all local references declared for elements in this scope.
10467
- for (const ref of scope.references) {
10468
- newOps.push(createVariableOp(view.tpl.allocateXrefId(), ref.variable, new ReferenceExpr(ref.targetId, ref.offset)));
10469
- }
10470
- if (scope.parent !== null) {
10471
- // Recursively add variables from the parent scope.
10472
- newOps.push(...generateVariablesInScopeForView(view, scope.parent));
11138
+ for (const op of ops) {
11139
+ transformExpressionsInOp(op, expr => {
11140
+ if (expr instanceof ContextExpr) {
11141
+ if (!scope.has(expr.view)) {
11142
+ throw new Error(`No context found for reference to view ${expr.view} from view ${view.xref}`);
11143
+ }
11144
+ return scope.get(expr.view);
11145
+ }
11146
+ else {
11147
+ return expr;
11148
+ }
11149
+ }, VisitorContextFlag.None);
10473
11150
  }
10474
- return newOps;
10475
11151
  }
10476
11152
 
10477
11153
  /**
@@ -10483,11 +11159,11 @@ function generateVariablesInScopeForView(view, scope) {
10483
11159
  */
10484
11160
  function phaseResolveNames(cpl) {
10485
11161
  for (const [_, view] of cpl.views) {
10486
- processLexicalScope$1(view, view.create, null);
10487
- processLexicalScope$1(view, view.update, null);
11162
+ processLexicalScope(view, view.create, null);
11163
+ processLexicalScope(view, view.update, null);
10488
11164
  }
10489
11165
  }
10490
- function processLexicalScope$1(view, ops, savedView) {
11166
+ function processLexicalScope(view, ops, savedView) {
10491
11167
  // Maps names defined in the lexical scope of this template to the `ir.XrefId`s of the variable
10492
11168
  // declarations which represent those values.
10493
11169
  //
@@ -10521,7 +11197,7 @@ function processLexicalScope$1(view, ops, savedView) {
10521
11197
  case OpKind.Listener:
10522
11198
  // Listener functions have separate variable declarations, so process them as a separate
10523
11199
  // lexical scope.
10524
- processLexicalScope$1(view, op.handlerOps, savedView);
11200
+ processLexicalScope(view, op.handlerOps, savedView);
10525
11201
  break;
10526
11202
  }
10527
11203
  }
@@ -10560,49 +11236,117 @@ function processLexicalScope$1(view, ops, savedView) {
10560
11236
  }
10561
11237
  }
10562
11238
 
10563
- /**
10564
- * Resolves `ir.ContextExpr` expressions (which represent embedded view or component contexts) to
10565
- * either the `ctx` parameter to component functions (for the current view context) or to variables
10566
- * that store those contexts (for contexts accessed via the `nextContext()` instruction).
10567
- */
10568
- function phaseResolveContexts(cpl) {
11239
+ function phaseSaveRestoreView(cpl) {
10569
11240
  for (const view of cpl.views.values()) {
10570
- processLexicalScope(view, view.create);
10571
- processLexicalScope(view, view.update);
11241
+ if (view === cpl.root) {
11242
+ // Save/restore operations are not necessary for the root view.
11243
+ continue;
11244
+ }
11245
+ view.create.prepend([
11246
+ createVariableOp(view.tpl.allocateXrefId(), {
11247
+ kind: SemanticVariableKind.SavedView,
11248
+ name: null,
11249
+ view: view.xref,
11250
+ }, new GetCurrentViewExpr()),
11251
+ ]);
11252
+ for (const op of view.create) {
11253
+ if (op.kind !== OpKind.Listener) {
11254
+ continue;
11255
+ }
11256
+ op.handlerOps.prepend([
11257
+ createVariableOp(view.tpl.allocateXrefId(), {
11258
+ kind: SemanticVariableKind.Context,
11259
+ name: null,
11260
+ view: view.xref,
11261
+ }, new RestoreViewExpr(view.xref)),
11262
+ ]);
11263
+ // The "restore view" operation in listeners requires a call to `resetView` to reset the
11264
+ // context prior to returning from the listener operation. Find any `return` statements in
11265
+ // the listener body and wrap them in a call to reset the view.
11266
+ for (const handlerOp of op.handlerOps) {
11267
+ if (handlerOp.kind === OpKind.Statement &&
11268
+ handlerOp.statement instanceof ReturnStatement) {
11269
+ handlerOp.statement.value = new ResetViewExpr(handlerOp.statement.value);
11270
+ }
11271
+ }
11272
+ }
10572
11273
  }
10573
11274
  }
10574
- function processLexicalScope(view, ops) {
10575
- // Track the expressions used to access all available contexts within the current view, by the
10576
- // view `ir.XrefId`.
10577
- const scope = new Map();
10578
- // The current view's context is accessible via the `ctx` parameter.
10579
- scope.set(view.xref, variable('ctx'));
10580
- for (const op of ops) {
10581
- switch (op.kind) {
10582
- case OpKind.Variable:
10583
- switch (op.variable.kind) {
10584
- case SemanticVariableKind.Context:
10585
- scope.set(op.variable.view, new ReadVariableExpr(op.xref));
10586
- break;
10587
- }
10588
- break;
10589
- case OpKind.Listener:
10590
- processLexicalScope(view, op.handlerOps);
10591
- break;
11275
+
11276
+ /**
11277
+ * Assign data slots for all operations which implement `ConsumesSlotOpTrait`, and propagate the
11278
+ * assigned data slots of those operations to any expressions which reference them via
11279
+ * `UsesSlotIndexTrait`.
11280
+ *
11281
+ * This phase is also responsible for counting the number of slots used for each view (its `decls`)
11282
+ * and propagating that number into the `Template` operations which declare embedded views.
11283
+ */
11284
+ function phaseSlotAllocation(cpl) {
11285
+ // Map of all declarations in all views within the component which require an assigned slot index.
11286
+ // This map needs to be global (across all views within the component) since it's possible to
11287
+ // reference a slot from one view from an expression within another (e.g. local references work
11288
+ // this way).
11289
+ const slotMap = new Map();
11290
+ // Process all views in the component and assign slot indexes.
11291
+ for (const [_, view] of cpl.views) {
11292
+ // Slot indices start at 0 for each view (and are not unique between views).
11293
+ let slotCount = 0;
11294
+ for (const op of view.create) {
11295
+ // Only consider declarations which consume data slots.
11296
+ if (!hasConsumesSlotTrait(op)) {
11297
+ continue;
11298
+ }
11299
+ // Assign slots to this declaration starting at the current `slotCount`.
11300
+ op.slot = slotCount;
11301
+ // And track its assigned slot in the `slotMap`.
11302
+ slotMap.set(op.xref, op.slot);
11303
+ // Each declaration may use more than 1 slot, so increment `slotCount` to reserve the number
11304
+ // of slots required.
11305
+ slotCount += op.numSlotsUsed;
10592
11306
  }
11307
+ // Record the total number of slots used on the view itself. This will later be propagated into
11308
+ // `ir.TemplateOp`s which declare those views (except for the root view).
11309
+ view.decls = slotCount;
10593
11310
  }
10594
- for (const op of ops) {
10595
- transformExpressionsInOp(op, expr => {
10596
- if (expr instanceof ContextExpr) {
10597
- if (!scope.has(expr.view)) {
10598
- throw new Error(`No context found for reference to view ${expr.view} from view ${view.xref}`);
10599
- }
10600
- return scope.get(expr.view);
11311
+ // After slot assignment, `slotMap` now contains slot assignments for every declaration in the
11312
+ // whole template, across all views. Next, look for expressions which implement
11313
+ // `UsesSlotIndexExprTrait` and propagate the assigned slot indexes into them.
11314
+ // Additionally, this second scan allows us to find `ir.TemplateOp`s which declare views and
11315
+ // propagate the number of slots used for each view into the operation which declares it.
11316
+ for (const [_, view] of cpl.views) {
11317
+ for (const op of view.ops()) {
11318
+ if (op.kind === OpKind.Template) {
11319
+ // Record the number of slots used by the view this `ir.TemplateOp` declares in the
11320
+ // operation itself, so it can be emitted later.
11321
+ const childView = cpl.views.get(op.xref);
11322
+ op.decls = childView.decls;
10601
11323
  }
10602
- else {
10603
- return expr;
11324
+ if (hasUsesSlotIndexTrait(op) && op.slot === null) {
11325
+ if (!slotMap.has(op.target)) {
11326
+ // We do expect to find a slot allocated for everything which might be referenced.
11327
+ throw new Error(`AssertionError: no slot allocated for ${OpKind[op.kind]} target ${op.target}`);
11328
+ }
11329
+ op.slot = slotMap.get(op.target);
10604
11330
  }
10605
- }, VisitorContextFlag.None);
11331
+ // Process all `ir.Expression`s within this view, and look for `usesSlotIndexExprTrait`.
11332
+ visitExpressionsInOp(op, expr => {
11333
+ if (!isIrExpression(expr)) {
11334
+ return;
11335
+ }
11336
+ if (!hasUsesSlotIndexTrait(expr) || expr.slot !== null) {
11337
+ return;
11338
+ }
11339
+ // The `UsesSlotIndexExprTrait` indicates that this expression references something declared
11340
+ // in this component template by its slot index. Use the `target` `ir.XrefId` to find the
11341
+ // allocated slot for that declaration in `slotMap`.
11342
+ if (!slotMap.has(expr.target)) {
11343
+ // We do expect to find a slot allocated for everything which might be referenced.
11344
+ throw new Error(`AssertionError: no slot allocated for ${expr.constructor.name} target ${expr.target}`);
11345
+ }
11346
+ // Record the allocated slot on the expression.
11347
+ expr.slot = slotMap.get(expr.target);
11348
+ });
11349
+ }
10606
11350
  }
10607
11351
  }
10608
11352
 
@@ -10927,429 +11671,123 @@ function tryInlineVariableInitializer(id, initializer, target, declFences) {
10927
11671
  }
10928
11672
  else if ((flags & VisitorContextFlag.InChildOperation) && (declFences & Fence.ViewContextRead)) {
10929
11673
  // We cannot inline variables that are sensitive to the current context across operation
10930
- // boundaries.
10931
- return expr;
10932
- }
10933
- switch (expr.kind) {
10934
- case ExpressionKind.ReadVariable:
10935
- if (expr.xref === id) {
10936
- // This is the usage site of the variable. Since nothing has disallowed inlining, it's
10937
- // safe to inline the initializer here.
10938
- inlined = true;
10939
- return initializer;
10940
- }
10941
- break;
10942
- default:
10943
- // For other types of `ir.Expression`s, whether inlining is allowed depends on their fences.
10944
- const exprFences = fencesForIrExpression(expr);
10945
- inliningAllowed = inliningAllowed && safeToInlinePastFences(exprFences, declFences);
10946
- break;
10947
- }
10948
- return expr;
10949
- }, VisitorContextFlag.None);
10950
- return inlined;
10951
- }
10952
- /**
10953
- * Determines whether inlining of `decl` should be allowed in "conservative" mode.
10954
- *
10955
- * In conservative mode, inlining behavior is limited to those operations which the
10956
- * `TemplateDefinitionBuilder` supported, with the goal of producing equivalent output.
10957
- */
10958
- function allowConservativeInlining(decl, target) {
10959
- // TODO(alxhub): understand exactly how TemplateDefinitionBuilder approaches inlining, and record
10960
- // that behavior here.
10961
- switch (decl.variable.kind) {
10962
- case SemanticVariableKind.Identifier:
10963
- return false;
10964
- case SemanticVariableKind.Context:
10965
- // Context can only be inlined into other variables.
10966
- return target.kind === OpKind.Variable;
10967
- default:
10968
- return true;
10969
- }
10970
- }
10971
-
10972
- const CHAINABLE = new Set([
10973
- Identifiers.elementStart,
10974
- Identifiers.elementEnd,
10975
- Identifiers.property,
10976
- Identifiers.elementContainerStart,
10977
- Identifiers.elementContainerEnd,
10978
- Identifiers.elementContainer,
10979
- ]);
10980
- /**
10981
- * Post-process a reified view compilation and convert sequential calls to chainable instructions
10982
- * into chain calls.
10983
- *
10984
- * For example, two `elementStart` operations in sequence:
10985
- *
10986
- * ```typescript
10987
- * elementStart(0, 'div');
10988
- * elementStart(1, 'span');
10989
- * ```
10990
- *
10991
- * Can be called as a chain instead:
10992
- *
10993
- * ```typescript
10994
- * elementStart(0, 'div')(1, 'span');
10995
- * ```
10996
- */
10997
- function phaseChaining(cpl) {
10998
- for (const [_, view] of cpl.views) {
10999
- chainOperationsInList(view.create);
11000
- chainOperationsInList(view.update);
11001
- }
11002
- }
11003
- function chainOperationsInList(opList) {
11004
- let chain = null;
11005
- for (const op of opList) {
11006
- if (op.kind !== OpKind.Statement || !(op.statement instanceof ExpressionStatement)) {
11007
- // This type of statement isn't chainable.
11008
- chain = null;
11009
- continue;
11010
- }
11011
- if (!(op.statement.expr instanceof InvokeFunctionExpr) ||
11012
- !(op.statement.expr.fn instanceof ExternalExpr)) {
11013
- // This is a statement, but not an instruction-type call, so not chainable.
11014
- chain = null;
11015
- continue;
11016
- }
11017
- const instruction = op.statement.expr.fn.value;
11018
- if (!CHAINABLE.has(instruction)) {
11019
- // This instruction isn't chainable.
11020
- chain = null;
11021
- continue;
11022
- }
11023
- // This instruction can be chained. It can either be added on to the previous chain (if
11024
- // compatible) or it can be the start of a new chain.
11025
- if (chain !== null && chain.instruction === instruction) {
11026
- // This instruction can be added onto the previous chain.
11027
- const expression = chain.expression.callFn(op.statement.expr.args, op.statement.expr.sourceSpan, op.statement.expr.pure);
11028
- chain.expression = expression;
11029
- chain.op.statement = expression.toStmt();
11030
- OpList.remove(op);
11031
- }
11032
- else {
11033
- // Leave this instruction alone for now, but consider it the start of a new chain.
11034
- chain = {
11035
- op,
11036
- instruction,
11037
- expression: op.statement.expr,
11038
- };
11039
- }
11040
- }
11041
- }
11042
-
11043
- /**
11044
- * Merges logically sequential `NextContextExpr` operations.
11045
- *
11046
- * `NextContextExpr` can be referenced repeatedly, "popping" the runtime's context stack each time.
11047
- * When two such expressions appear back-to-back, it's possible to merge them together into a single
11048
- * `NextContextExpr` that steps multiple contexts. This merging is possible if all conditions are
11049
- * met:
11050
- *
11051
- * * The result of the `NextContextExpr` that's folded into the subsequent one is not stored (that
11052
- * is, the call is purely side-effectful).
11053
- * * No operations in between them uses the implicit context.
11054
- */
11055
- function phaseMergeNextContext(cpl) {
11056
- for (const view of cpl.views.values()) {
11057
- for (const op of view.create) {
11058
- if (op.kind === OpKind.Listener) {
11059
- mergeNextContextsInOps(op.handlerOps);
11060
- }
11061
- }
11062
- mergeNextContextsInOps(view.update);
11063
- }
11064
- }
11065
- function mergeNextContextsInOps(ops) {
11066
- for (const op of ops) {
11067
- // Look for a candidate operation to maybe merge.
11068
- if (op.kind !== OpKind.Statement || !(op.statement instanceof ExpressionStatement) ||
11069
- !(op.statement.expr instanceof NextContextExpr)) {
11070
- continue;
11071
- }
11072
- const mergeSteps = op.statement.expr.steps;
11073
- // Try to merge this `ir.NextContextExpr`.
11074
- let tryToMerge = true;
11075
- for (let candidate = op.next; candidate.kind !== OpKind.ListEnd && tryToMerge; candidate = candidate.next) {
11076
- visitExpressionsInOp(candidate, (expr, flags) => {
11077
- if (!isIrExpression(expr)) {
11078
- return expr;
11079
- }
11080
- if (!tryToMerge) {
11081
- // Either we've already merged, or failed to merge.
11082
- return;
11083
- }
11084
- if (flags & VisitorContextFlag.InChildOperation) {
11085
- // We cannot merge into child operations.
11086
- return;
11087
- }
11088
- switch (expr.kind) {
11089
- case ExpressionKind.NextContext:
11090
- // Merge the previous `ir.NextContextExpr` into this one.
11091
- expr.steps += mergeSteps;
11092
- OpList.remove(op);
11093
- tryToMerge = false;
11094
- break;
11095
- case ExpressionKind.GetCurrentView:
11096
- case ExpressionKind.Reference:
11097
- // Can't merge past a dependency on the context.
11098
- tryToMerge = false;
11099
- break;
11100
- }
11101
- });
11102
- }
11103
- }
11104
- }
11105
-
11106
- const CONTAINER_TAG = 'ng-container';
11107
- /**
11108
- * Replace an `Element` or `ElementStart` whose tag is `ng-container` with a specific op.
11109
- */
11110
- function phaseNgContainer(cpl) {
11111
- for (const [_, view] of cpl.views) {
11112
- const updatedElementXrefs = new Set();
11113
- for (const op of view.create) {
11114
- if (op.kind === OpKind.ElementStart && op.tag === CONTAINER_TAG) {
11115
- // Transmute the `ElementStart` instruction to `ContainerStart`.
11116
- op.kind = OpKind.ContainerStart;
11117
- updatedElementXrefs.add(op.xref);
11118
- }
11119
- if (op.kind === OpKind.ElementEnd && updatedElementXrefs.has(op.xref)) {
11120
- // This `ElementEnd` is associated with an `ElementStart` we already transmuted.
11121
- op.kind = OpKind.ContainerEnd;
11122
- }
11123
- }
11124
- }
11125
- }
11126
-
11127
- function phaseSaveRestoreView(cpl) {
11128
- for (const view of cpl.views.values()) {
11129
- if (view === cpl.root) {
11130
- // Save/restore operations are not necessary for the root view.
11131
- continue;
11132
- }
11133
- view.create.prepend([
11134
- createVariableOp(view.tpl.allocateXrefId(), {
11135
- kind: SemanticVariableKind.SavedView,
11136
- name: null,
11137
- view: view.xref,
11138
- }, new GetCurrentViewExpr()),
11139
- ]);
11140
- for (const op of view.create) {
11141
- if (op.kind !== OpKind.Listener) {
11142
- continue;
11143
- }
11144
- op.handlerOps.prepend([
11145
- createVariableOp(view.tpl.allocateXrefId(), {
11146
- kind: SemanticVariableKind.Context,
11147
- name: null,
11148
- view: view.xref,
11149
- }, new RestoreViewExpr(view.xref)),
11150
- ]);
11151
- // The "restore view" operation in listeners requires a call to `resetView` to reset the
11152
- // context prior to returning from the listener operation. Find any `return` statements in
11153
- // the listener body and wrap them in a call to reset the view.
11154
- for (const handlerOp of op.handlerOps) {
11155
- if (handlerOp.kind === OpKind.Statement &&
11156
- handlerOp.statement instanceof ReturnStatement) {
11157
- handlerOp.statement.value = new ResetViewExpr(handlerOp.statement.value);
11674
+ // boundaries.
11675
+ return expr;
11676
+ }
11677
+ switch (expr.kind) {
11678
+ case ExpressionKind.ReadVariable:
11679
+ if (expr.xref === id) {
11680
+ // This is the usage site of the variable. Since nothing has disallowed inlining, it's
11681
+ // safe to inline the initializer here.
11682
+ inlined = true;
11683
+ return initializer;
11158
11684
  }
11159
- }
11685
+ break;
11686
+ default:
11687
+ // For other types of `ir.Expression`s, whether inlining is allowed depends on their fences.
11688
+ const exprFences = fencesForIrExpression(expr);
11689
+ inliningAllowed = inliningAllowed && safeToInlinePastFences(exprFences, declFences);
11690
+ break;
11160
11691
  }
11692
+ return expr;
11693
+ }, VisitorContextFlag.None);
11694
+ return inlined;
11695
+ }
11696
+ /**
11697
+ * Determines whether inlining of `decl` should be allowed in "conservative" mode.
11698
+ *
11699
+ * In conservative mode, inlining behavior is limited to those operations which the
11700
+ * `TemplateDefinitionBuilder` supported, with the goal of producing equivalent output.
11701
+ */
11702
+ function allowConservativeInlining(decl, target) {
11703
+ // TODO(alxhub): understand exactly how TemplateDefinitionBuilder approaches inlining, and record
11704
+ // that behavior here.
11705
+ switch (decl.variable.kind) {
11706
+ case SemanticVariableKind.Identifier:
11707
+ return false;
11708
+ case SemanticVariableKind.Context:
11709
+ // Context can only be inlined into other variables.
11710
+ return target.kind === OpKind.Variable;
11711
+ default:
11712
+ return true;
11161
11713
  }
11162
11714
  }
11163
11715
 
11164
- function phasePureFunctionExtraction(cpl) {
11165
- for (const view of cpl.views.values()) {
11716
+ /**
11717
+ * Finds all unresolved safe read expressions, and converts them into the appropriate output AST
11718
+ * reads, guarded by null checks.
11719
+ */
11720
+ function phaseExpandSafeReads(cpl) {
11721
+ for (const [_, view] of cpl.views) {
11166
11722
  for (const op of view.ops()) {
11167
- visitExpressionsInOp(op, expr => {
11168
- if (!(expr instanceof PureFunctionExpr) || expr.body === null) {
11169
- return;
11170
- }
11171
- const constantDef = new PureFunctionConstant(expr.args.length);
11172
- expr.fn = cpl.pool.getSharedConstant(constantDef, expr.body);
11173
- expr.body = null;
11174
- });
11723
+ transformExpressionsInOp(op, safeTransform, VisitorContextFlag.None);
11724
+ transformExpressionsInOp(op, ternaryTransform, VisitorContextFlag.None);
11175
11725
  }
11176
11726
  }
11177
11727
  }
11178
- class PureFunctionConstant extends GenericKeyFn {
11179
- constructor(numArgs) {
11180
- super();
11181
- this.numArgs = numArgs;
11182
- }
11183
- keyOf(expr) {
11184
- if (expr instanceof PureFunctionParameterExpr) {
11185
- return `param(${expr.index})`;
11186
- }
11187
- else {
11188
- return super.keyOf(expr);
11189
- }
11190
- }
11191
- toSharedConstantDeclaration(declName, keyExpr) {
11192
- const fnParams = [];
11193
- for (let idx = 0; idx < this.numArgs; idx++) {
11194
- fnParams.push(new FnParam('_p' + idx));
11195
- }
11196
- // We will never visit `ir.PureFunctionParameterExpr`s that don't belong to us, because this
11197
- // transform runs inside another visitor which will visit nested pure functions before this one.
11198
- const returnExpr = transformExpressionsInExpression(keyExpr, expr => {
11199
- if (!(expr instanceof PureFunctionParameterExpr)) {
11200
- return expr;
11201
- }
11202
- return variable('_p' + expr.index);
11203
- }, VisitorContextFlag.None);
11204
- return new DeclareFunctionStmt(declName, fnParams, [new ReturnStatement(returnExpr)]);
11205
- }
11728
+ function isSafeAccessExpression(e) {
11729
+ return e instanceof SafePropertyReadExpr || e instanceof SafeKeyedReadExpr;
11206
11730
  }
11207
-
11208
- function phasePipeCreation(cpl) {
11209
- for (const view of cpl.views.values()) {
11210
- processPipeBindingsInView(view);
11211
- }
11731
+ function isUnsafeAccessExpression(e) {
11732
+ return e instanceof ReadPropExpr || e instanceof ReadKeyExpr;
11212
11733
  }
11213
- function processPipeBindingsInView(view) {
11214
- for (const updateOp of view.update) {
11215
- visitExpressionsInOp(updateOp, (expr, flags) => {
11216
- if (!isIrExpression(expr)) {
11217
- return;
11218
- }
11219
- if (expr.kind !== ExpressionKind.PipeBinding) {
11220
- return;
11221
- }
11222
- if (flags & VisitorContextFlag.InChildOperation) {
11223
- throw new Error(`AssertionError: pipe bindings should not appear in child expressions`);
11224
- }
11225
- if (!hasDependsOnSlotContextTrait(updateOp)) {
11226
- throw new Error(`AssertionError: pipe binding associated with non-slot operation ${OpKind[updateOp.kind]}`);
11227
- }
11228
- addPipeToCreationBlock(view, updateOp.target, expr);
11229
- });
11230
- }
11734
+ function isAccessExpression(e) {
11735
+ return isSafeAccessExpression(e) || isUnsafeAccessExpression(e);
11231
11736
  }
11232
- function addPipeToCreationBlock(view, afterTargetXref, binding) {
11233
- // Find the appropriate point to insert the Pipe creation operation.
11234
- // We're looking for `afterTargetXref` (and also want to insert after any other pipe operations
11235
- // which might be beyond it).
11236
- for (let op = view.create.head.next; op.kind !== OpKind.ListEnd; op = op.next) {
11237
- if (!hasConsumesSlotTrait(op)) {
11238
- continue;
11239
- }
11240
- if (op.xref !== afterTargetXref) {
11241
- continue;
11737
+ function deepestSafeTernary(e) {
11738
+ if (isAccessExpression(e) && e.receiver instanceof SafeTernaryExpr) {
11739
+ let st = e.receiver;
11740
+ while (st.expr instanceof SafeTernaryExpr) {
11741
+ st = st.expr;
11242
11742
  }
11243
- // We've found a tentative insertion point; however, we also want to skip past any _other_ pipe
11244
- // operations present.
11245
- while (op.next.kind === OpKind.Pipe) {
11246
- op = op.next;
11247
- }
11248
- const pipe = createPipeOp(binding.target, binding.name);
11249
- OpList.insertBefore(pipe, op.next);
11250
- // This completes adding the pipe to the creation block.
11251
- return;
11743
+ return st;
11252
11744
  }
11253
- // At this point, we've failed to add the pipe to the creation block.
11254
- throw new Error(`AssertionError: unable to find insertion point for pipe ${binding.name}`);
11745
+ return null;
11255
11746
  }
11256
-
11257
- function phasePipeVariadic(cpl) {
11258
- for (const view of cpl.views.values()) {
11259
- for (const op of view.update) {
11260
- transformExpressionsInOp(op, expr => {
11261
- if (!(expr instanceof PipeBindingExpr)) {
11262
- return expr;
11263
- }
11264
- // Pipes are variadic if they have more than 4 arguments.
11265
- if (expr.args.length <= 4) {
11266
- return expr;
11267
- }
11268
- return new PipeBindingVariadicExpr(expr.target, expr.name, literalArr(expr.args), expr.args.length);
11269
- }, VisitorContextFlag.None);
11270
- }
11747
+ // TODO: When strict compatibility with TemplateDefinitionBuilder is not required, we can use `&&`
11748
+ // instead.
11749
+ function safeTransform(e) {
11750
+ if (e instanceof SafeInvokeFunctionExpr) {
11751
+ // TODO: Implement safe function calls in a subsequent commit.
11752
+ return new InvokeFunctionExpr(e.receiver, e.args);
11271
11753
  }
11272
- }
11273
-
11274
- function phasePureLiteralStructures(cpl) {
11275
- for (const view of cpl.views.values()) {
11276
- for (const op of view.update) {
11277
- transformExpressionsInOp(op, (expr, flags) => {
11278
- if (flags & VisitorContextFlag.InChildOperation) {
11279
- return expr;
11280
- }
11281
- if (expr instanceof LiteralArrayExpr) {
11282
- return transformLiteralArray(expr);
11283
- }
11284
- else if (expr instanceof LiteralMapExpr) {
11285
- return transformLiteralMap(expr);
11286
- }
11287
- return expr;
11288
- }, VisitorContextFlag.None);
11289
- }
11754
+ if (!isAccessExpression(e)) {
11755
+ return e;
11290
11756
  }
11291
- }
11292
- function transformLiteralArray(expr) {
11293
- const derivedEntries = [];
11294
- const nonConstantArgs = [];
11295
- for (const entry of expr.entries) {
11296
- if (entry.isConstant()) {
11297
- derivedEntries.push(entry);
11757
+ const dst = deepestSafeTernary(e);
11758
+ if (dst) {
11759
+ if (e instanceof ReadPropExpr) {
11760
+ dst.expr = dst.expr.prop(e.name);
11761
+ return e.receiver;
11298
11762
  }
11299
- else {
11300
- const idx = nonConstantArgs.length;
11301
- nonConstantArgs.push(entry);
11302
- derivedEntries.push(new PureFunctionParameterExpr(idx));
11763
+ if (e instanceof ReadKeyExpr) {
11764
+ dst.expr = dst.expr.key(e.index);
11765
+ return e.receiver;
11766
+ }
11767
+ if (e instanceof SafePropertyReadExpr) {
11768
+ dst.expr = new SafeTernaryExpr(dst.expr.clone(), dst.expr.prop(e.name));
11769
+ return e.receiver;
11770
+ }
11771
+ if (e instanceof SafeKeyedReadExpr) {
11772
+ dst.expr = new SafeTernaryExpr(dst.expr.clone(), dst.expr.key(e.index));
11773
+ return e.receiver;
11303
11774
  }
11304
11775
  }
11305
- return new PureFunctionExpr(literalArr(derivedEntries), nonConstantArgs);
11306
- }
11307
- function transformLiteralMap(expr) {
11308
- let derivedEntries = [];
11309
- const nonConstantArgs = [];
11310
- for (const entry of expr.entries) {
11311
- if (entry.value.isConstant()) {
11312
- derivedEntries.push(entry);
11776
+ else {
11777
+ if (e instanceof SafePropertyReadExpr) {
11778
+ return new SafeTernaryExpr(e.receiver.clone(), e.receiver.prop(e.name));
11313
11779
  }
11314
- else {
11315
- const idx = nonConstantArgs.length;
11316
- nonConstantArgs.push(entry.value);
11317
- derivedEntries.push(new LiteralMapEntry(entry.key, new PureFunctionParameterExpr(idx), entry.quoted));
11780
+ if (e instanceof SafeKeyedReadExpr) {
11781
+ return new SafeTernaryExpr(e.receiver.clone(), e.receiver.key(e.index));
11318
11782
  }
11319
11783
  }
11320
- return new PureFunctionExpr(literalMap(derivedEntries), nonConstantArgs);
11784
+ return e;
11321
11785
  }
11322
-
11323
- function phaseAlignPipeVariadicVarOffset(cpl) {
11324
- for (const view of cpl.views.values()) {
11325
- for (const op of view.update) {
11326
- visitExpressionsInOp(op, expr => {
11327
- if (!(expr instanceof PipeBindingVariadicExpr)) {
11328
- return expr;
11329
- }
11330
- if (!(expr.args instanceof PureFunctionExpr)) {
11331
- return expr;
11332
- }
11333
- if (expr.varOffset === null || expr.args.varOffset === null) {
11334
- throw new Error(`Must run after variable counting`);
11335
- }
11336
- // The structure of this variadic pipe expression is:
11337
- // PipeBindingVariadic(#, Y, PureFunction(X, ...ARGS))
11338
- // Where X and Y are the slot offsets for the variables used by these operations, and Y > X.
11339
- // In `TemplateDefinitionBuilder` the PipeBindingVariadic variable slots are allocated
11340
- // before the PureFunction slots, which is unusually out-of-order.
11341
- //
11342
- // To maintain identical output for the tests in question, we adjust the variable offsets of
11343
- // these two calls to emulate TDB's behavior. This is not perfect, because the ARGS of the
11344
- // PureFunction call may also allocate slots which by TDB's ordering would come after X, and
11345
- // we don't account for that. Still, this should be enough to pass the existing pipe tests.
11346
- // Put the PipeBindingVariadic vars where the PureFunction vars were previously allocated.
11347
- expr.varOffset = expr.args.varOffset;
11348
- // Put the PureFunction vars following the PipeBindingVariadic vars.
11349
- expr.args.varOffset = expr.varOffset + varsUsedByIrExpression(expr);
11350
- });
11351
- }
11786
+ function ternaryTransform(e) {
11787
+ if (!(e instanceof SafeTernaryExpr)) {
11788
+ return e;
11352
11789
  }
11790
+ return new ConditionalExpr(new BinaryOperatorExpr(BinaryOperator.Equals, e.guard, NULL_EXPR), NULL_EXPR, e.expr);
11353
11791
  }
11354
11792
 
11355
11793
  /**
@@ -11357,6 +11795,7 @@ function phaseAlignPipeVariadicVarOffset(cpl) {
11357
11795
  * processing, the compilation should be in a state where it can be emitted via `emitTemplateFn`.s
11358
11796
  */
11359
11797
  function transformTemplate(cpl) {
11798
+ phaseAttributeExtraction(cpl, true);
11360
11799
  phasePipeCreation(cpl);
11361
11800
  phasePipeVariadic(cpl);
11362
11801
  phasePureLiteralStructures(cpl);
@@ -11366,6 +11805,8 @@ function transformTemplate(cpl) {
11366
11805
  phaseResolveContexts(cpl);
11367
11806
  phaseLocalRefs(cpl);
11368
11807
  phaseConstCollection(cpl);
11808
+ phaseNullishCoalescing(cpl);
11809
+ phaseExpandSafeReads(cpl);
11369
11810
  phaseSlotAllocation(cpl);
11370
11811
  phaseVarCounting(cpl);
11371
11812
  phaseGenerateAdvance(cpl);
@@ -11618,7 +12059,6 @@ function ingestElement(view, element) {
11618
12059
  const id = view.tpl.allocateXrefId();
11619
12060
  const startOp = createElementStartOp(element.name, id);
11620
12061
  view.create.push(startOp);
11621
- ingestAttributes(startOp, element);
11622
12062
  ingestBindings(view, startOp, element);
11623
12063
  ingestReferences(startOp, element);
11624
12064
  ingestNodes(view, element.children);
@@ -11632,7 +12072,6 @@ function ingestTemplate(view, tmpl) {
11632
12072
  // TODO: validate the fallback tag name here.
11633
12073
  const tplOp = createTemplateOp(childView.xref, tmpl.tagName ?? 'ng-template');
11634
12074
  view.create.push(tplOp);
11635
- ingestAttributes(tplOp, tmpl);
11636
12075
  ingestBindings(view, tplOp, tmpl);
11637
12076
  ingestReferences(tplOp, tmpl);
11638
12077
  ingestNodes(childView, tmpl.children);
@@ -11722,36 +12161,27 @@ function convertAst(ast, cpl) {
11722
12161
  else if (ast instanceof Conditional) {
11723
12162
  return new ConditionalExpr(convertAst(ast.condition, cpl), convertAst(ast.trueExp, cpl), convertAst(ast.falseExp, cpl));
11724
12163
  }
12164
+ else if (ast instanceof NonNullAssert) {
12165
+ // A non-null assertion shouldn't impact generated instructions, so we can just drop it.
12166
+ return convertAst(ast.expression, cpl);
12167
+ }
11725
12168
  else if (ast instanceof BindingPipe) {
11726
12169
  return new PipeBindingExpr(cpl.allocateXrefId(), ast.name, [
11727
12170
  convertAst(ast.exp, cpl),
11728
12171
  ...ast.args.map(arg => convertAst(arg, cpl)),
11729
12172
  ]);
11730
12173
  }
11731
- else {
11732
- throw new Error(`Unhandled expression type: ${ast.constructor.name}`);
11733
- }
11734
- }
11735
- /**
11736
- * Process all of the attributes on an element-like structure in the template AST and convert them
11737
- * to their IR representation.
11738
- */
11739
- function ingestAttributes(op, element) {
11740
- assertIsElementAttributes(op.attributes);
11741
- for (const attr of element.attributes) {
11742
- op.attributes.add(ElementAttributeKind.Attribute, attr.name, literal(attr.value));
12174
+ else if (ast instanceof SafeKeyedRead) {
12175
+ return new SafeKeyedReadExpr(convertAst(ast.receiver, cpl), convertAst(ast.key, cpl));
11743
12176
  }
11744
- for (const input of element.inputs) {
11745
- op.attributes.add(ElementAttributeKind.Binding, input.name, null);
12177
+ else if (ast instanceof SafePropertyRead) {
12178
+ return new SafePropertyReadExpr(convertAst(ast.receiver, cpl), ast.name);
11746
12179
  }
11747
- for (const output of element.outputs) {
11748
- op.attributes.add(ElementAttributeKind.Binding, output.name, null);
12180
+ else if (ast instanceof SafeCall) {
12181
+ return new SafeInvokeFunctionExpr(convertAst(ast.receiver, cpl), ast.args.map(a => convertAst(a, cpl)));
11749
12182
  }
11750
- if (element instanceof Template) {
11751
- for (const attr of element.templateAttrs) {
11752
- // TODO: what do we do about the value here?
11753
- op.attributes.add(ElementAttributeKind.Template, attr.name, null);
11754
- }
12183
+ else {
12184
+ throw new Error(`Unhandled expression type: ${ast.constructor.name}`);
11755
12185
  }
11756
12186
  }
11757
12187
  /**
@@ -11760,60 +12190,77 @@ function ingestAttributes(op, element) {
11760
12190
  */
11761
12191
  function ingestBindings(view, op, element) {
11762
12192
  if (element instanceof Template) {
11763
- for (const attr of [...element.templateAttrs, ...element.inputs]) {
11764
- if (!(attr instanceof BoundAttribute)) {
11765
- continue;
12193
+ for (const attr of element.templateAttrs) {
12194
+ if (attr instanceof TextAttribute) {
12195
+ view.update.push(createAttributeOp(op.xref, ElementAttributeKind.Template, attr.name, literal(attr.value)));
12196
+ }
12197
+ else {
12198
+ ingestPropertyBinding(view, op.xref, ElementAttributeKind.Template, attr);
11766
12199
  }
11767
- ingestPropertyBinding(view, op.xref, attr.name, attr.value);
11768
12200
  }
11769
12201
  }
11770
- else {
11771
- for (const input of element.inputs) {
11772
- ingestPropertyBinding(view, op.xref, input.name, input.value);
12202
+ for (const attr of element.attributes) {
12203
+ view.update.push(createAttributeOp(op.xref, ElementAttributeKind.Attribute, attr.name, literal(attr.value)));
12204
+ }
12205
+ for (const input of element.inputs) {
12206
+ ingestPropertyBinding(view, op.xref, ElementAttributeKind.Binding, input);
12207
+ }
12208
+ for (const output of element.outputs) {
12209
+ const listenerOp = createListenerOp(op.xref, output.name, op.tag);
12210
+ // if output.handler is a chain, then push each statement from the chain separately, and
12211
+ // return the last one?
12212
+ let inputExprs;
12213
+ let handler = output.handler;
12214
+ if (handler instanceof ASTWithSource) {
12215
+ handler = handler.ast;
11773
12216
  }
11774
- for (const output of element.outputs) {
11775
- const listenerOp = createListenerOp(op.xref, output.name, op.tag);
11776
- // if output.handler is a chain, then push each statement from the chain separately, and
11777
- // return the last one?
11778
- let inputExprs;
11779
- let handler = output.handler;
11780
- if (handler instanceof ASTWithSource) {
11781
- handler = handler.ast;
11782
- }
11783
- if (handler instanceof Chain) {
11784
- inputExprs = handler.expressions;
11785
- }
11786
- else {
11787
- inputExprs = [handler];
11788
- }
11789
- if (inputExprs.length === 0) {
11790
- throw new Error('Expected listener to have non-empty expression list.');
11791
- }
11792
- const expressions = inputExprs.map(expr => convertAst(expr, view.tpl));
11793
- const returnExpr = expressions.pop();
11794
- for (const expr of expressions) {
11795
- const stmtOp = createStatementOp(new ExpressionStatement(expr));
11796
- listenerOp.handlerOps.push(stmtOp);
11797
- }
11798
- listenerOp.handlerOps.push(createStatementOp(new ReturnStatement(returnExpr)));
11799
- view.create.push(listenerOp);
12217
+ if (handler instanceof Chain) {
12218
+ inputExprs = handler.expressions;
12219
+ }
12220
+ else {
12221
+ inputExprs = [handler];
11800
12222
  }
12223
+ if (inputExprs.length === 0) {
12224
+ throw new Error('Expected listener to have non-empty expression list.');
12225
+ }
12226
+ const expressions = inputExprs.map(expr => convertAst(expr, view.tpl));
12227
+ const returnExpr = expressions.pop();
12228
+ for (const expr of expressions) {
12229
+ const stmtOp = createStatementOp(new ExpressionStatement(expr));
12230
+ listenerOp.handlerOps.push(stmtOp);
12231
+ }
12232
+ listenerOp.handlerOps.push(createStatementOp(new ReturnStatement(returnExpr)));
12233
+ view.create.push(listenerOp);
11801
12234
  }
11802
12235
  }
11803
- function ingestPropertyBinding(view, xref, name, value) {
12236
+ function ingestPropertyBinding(view, xref, bindingKind, { name, value, type }) {
11804
12237
  if (value instanceof ASTWithSource) {
11805
12238
  value = value.ast;
11806
12239
  }
11807
12240
  if (value instanceof Interpolation) {
11808
- view.update.push(createInterpolatePropertyOp(xref, name, value.strings, value.expressions.map(expr => convertAst(expr, view.tpl))));
12241
+ switch (type) {
12242
+ case 0 /* e.BindingType.Property */:
12243
+ view.update.push(createInterpolatePropertyOp(xref, bindingKind, name, value.strings, value.expressions.map(expr => convertAst(expr, view.tpl))));
12244
+ break;
12245
+ default:
12246
+ // TODO: implement remaining binding types.
12247
+ throw Error(`Interpolated property binding type not handled: ${type}`);
12248
+ }
11809
12249
  }
11810
12250
  else {
11811
- view.update.push(createPropertyOp(xref, name, convertAst(value, view.tpl)));
12251
+ switch (type) {
12252
+ case 0 /* e.BindingType.Property */:
12253
+ view.update.push(createPropertyOp(xref, bindingKind, name, convertAst(value, view.tpl)));
12254
+ break;
12255
+ default:
12256
+ // TODO: implement remaining binding types.
12257
+ throw Error(`Property binding type not handled: ${type}`);
12258
+ }
11812
12259
  }
11813
12260
  }
11814
12261
  /**
11815
- * Process all of the local references on an element-like structure in the template AST and convert
11816
- * them to their IR representation.
12262
+ * Process all of the local references on an element-like structure in the template AST and
12263
+ * convert them to their IR representation.
11817
12264
  */
11818
12265
  function ingestReferences(op, element) {
11819
12266
  assertIsArray(op.localRefs);
@@ -23369,7 +23816,7 @@ function publishFacade(global) {
23369
23816
  * @description
23370
23817
  * Entry point for all public APIs of the compiler package.
23371
23818
  */
23372
- const VERSION = new Version('16.1.1');
23819
+ const VERSION = new Version('16.2.0-next.0');
23373
23820
 
23374
23821
  class CompilerConfig {
23375
23822
  constructor({ defaultEncapsulation = ViewEncapsulation.Emulated, useJit = true, missingTranslation = null, preserveWhitespaces, strictInjectionParameters } = {}) {
@@ -25297,7 +25744,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$6 = '12.0.0';
25297
25744
  function compileDeclareClassMetadata(metadata) {
25298
25745
  const definitionMap = new DefinitionMap();
25299
25746
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$6));
25300
- definitionMap.set('version', literal('16.1.1'));
25747
+ definitionMap.set('version', literal('16.2.0-next.0'));
25301
25748
  definitionMap.set('ngImport', importExpr(Identifiers.core));
25302
25749
  definitionMap.set('type', metadata.type);
25303
25750
  definitionMap.set('decorators', metadata.decorators);
@@ -25400,7 +25847,7 @@ function compileDeclareDirectiveFromMetadata(meta) {
25400
25847
  function createDirectiveDefinitionMap(meta) {
25401
25848
  const definitionMap = new DefinitionMap();
25402
25849
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$5));
25403
- definitionMap.set('version', literal('16.1.1'));
25850
+ definitionMap.set('version', literal('16.2.0-next.0'));
25404
25851
  // e.g. `type: MyDirective`
25405
25852
  definitionMap.set('type', meta.type.value);
25406
25853
  if (meta.isStandalone) {
@@ -25628,7 +26075,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$4 = '12.0.0';
25628
26075
  function compileDeclareFactoryFunction(meta) {
25629
26076
  const definitionMap = new DefinitionMap();
25630
26077
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$4));
25631
- definitionMap.set('version', literal('16.1.1'));
26078
+ definitionMap.set('version', literal('16.2.0-next.0'));
25632
26079
  definitionMap.set('ngImport', importExpr(Identifiers.core));
25633
26080
  definitionMap.set('type', meta.type.value);
25634
26081
  definitionMap.set('deps', compileDependencies(meta.deps));
@@ -25663,7 +26110,7 @@ function compileDeclareInjectableFromMetadata(meta) {
25663
26110
  function createInjectableDefinitionMap(meta) {
25664
26111
  const definitionMap = new DefinitionMap();
25665
26112
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$3));
25666
- definitionMap.set('version', literal('16.1.1'));
26113
+ definitionMap.set('version', literal('16.2.0-next.0'));
25667
26114
  definitionMap.set('ngImport', importExpr(Identifiers.core));
25668
26115
  definitionMap.set('type', meta.type.value);
25669
26116
  // Only generate providedIn property if it has a non-null value
@@ -25714,7 +26161,7 @@ function compileDeclareInjectorFromMetadata(meta) {
25714
26161
  function createInjectorDefinitionMap(meta) {
25715
26162
  const definitionMap = new DefinitionMap();
25716
26163
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$2));
25717
- definitionMap.set('version', literal('16.1.1'));
26164
+ definitionMap.set('version', literal('16.2.0-next.0'));
25718
26165
  definitionMap.set('ngImport', importExpr(Identifiers.core));
25719
26166
  definitionMap.set('type', meta.type.value);
25720
26167
  definitionMap.set('providers', meta.providers);
@@ -25744,7 +26191,7 @@ function compileDeclareNgModuleFromMetadata(meta) {
25744
26191
  function createNgModuleDefinitionMap(meta) {
25745
26192
  const definitionMap = new DefinitionMap();
25746
26193
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$1));
25747
- definitionMap.set('version', literal('16.1.1'));
26194
+ definitionMap.set('version', literal('16.2.0-next.0'));
25748
26195
  definitionMap.set('ngImport', importExpr(Identifiers.core));
25749
26196
  definitionMap.set('type', meta.type.value);
25750
26197
  // We only generate the keys in the metadata if the arrays contain values.
@@ -25795,7 +26242,7 @@ function compileDeclarePipeFromMetadata(meta) {
25795
26242
  function createPipeDefinitionMap(meta) {
25796
26243
  const definitionMap = new DefinitionMap();
25797
26244
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION));
25798
- definitionMap.set('version', literal('16.1.1'));
26245
+ definitionMap.set('version', literal('16.2.0-next.0'));
25799
26246
  definitionMap.set('ngImport', importExpr(Identifiers.core));
25800
26247
  // e.g. `type: MyPipe`
25801
26248
  definitionMap.set('type', meta.type.value);