refactor(compiler): integrate let declarations into the template pipeline (#56299)

These changes integrate let declarations into the template pipeline. This involves a few operations:
* Producing a `declareLet` instruction call at creation time to initialize the declaration.
* Producing a `storeLet` instruction call in the place of the let declaration, including the necessary `advance` calls beforehand.
* For let declarations used within their declaration view, moving the `const` to be placed right after the `storeLet` call to ensure the their value has been computed.
* For let declarations that are _only_ used in their declaration view, removing the `storeLet` call and inlining the expression into the constant statement.

PR Close #56299
This commit is contained in:
Kristiyan Kostadinov 2024-06-08 09:52:15 +02:00 committed by Andrew Kushnir
parent cc6cd08ca3
commit bc655bf309
62 changed files with 2286 additions and 26 deletions

View file

@ -104,6 +104,7 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression>
// Enable the new block syntax if compiled with v17 and
// above, or when using the local placeholder version.
const enableBlockSyntax = semver.major(version) >= 17 || version === PLACEHOLDER_VERSION;
const enableLetSyntax = version === PLACEHOLDER_VERSION;
const template = parseTemplate(templateInfo.code, templateInfo.sourceUrl, {
escapedString: templateInfo.isEscaped,
@ -116,6 +117,7 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression>
// We normalize line endings if the template is was inline.
i18nNormalizeLineEndingsInICUs: isInline,
enableBlockSyntax,
enableLetSyntax,
});
if (template.errors !== null) {
const errors = template.errors.map((err) => err.toString()).join('\n');

View file

@ -0,0 +1,717 @@
/****************************************************************************************************
* PARTIAL FILE: simple_let.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 1;
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@let result = value * 2;
The result is {{result}}
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
@let result = value * 2;
The result is {{result}}
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: simple_let.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: multiple_let.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 1;
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@let one = value + 1;
@let two = one + 1;
@let result = two + 1;
The result is {{result}}
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
@let one = value + 1;
@let two = one + 1;
@let result = two + 1;
The result is {{result}}
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: multiple_let.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_with_pipe.js
****************************************************************************************************/
import { Component, Pipe } from '@angular/core';
import * as i0 from "@angular/core";
export class DoublePipe {
transform(value) {
return value * 2;
}
}
DoublePipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: DoublePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
DoublePipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: DoublePipe, isStandalone: true, name: "double" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: DoublePipe, decorators: [{
type: Pipe,
args: [{
name: 'double',
standalone: true
}]
}] });
export class MyApp {
constructor() {
this.value = 1;
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@let one = value + 1;
@let result = one | double;
The result is {{result}}
`, isInline: true, dependencies: [{ kind: "pipe", type: DoublePipe, name: "double" }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
@let one = value + 1;
@let result = one | double;
The result is {{result}}
`,
standalone: true,
imports: [DoublePipe]
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_with_pipe.d.ts
****************************************************************************************************/
import { PipeTransform } from '@angular/core';
import * as i0 from "@angular/core";
export declare class DoublePipe implements PipeTransform {
transform(value: number): number;
static ɵfac: i0.ɵɵFactoryDeclaration<DoublePipe, never>;
static ɵpipe: i0.ɵɵPipeDeclaration<DoublePipe, "double", true>;
}
export declare class MyApp {
value: number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_in_listener.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 1;
}
callback(one, two) {
console.log(one, two);
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@let one = value + 1;
@let two = one + 1;
<button (click)="callback(one, two)"></button>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
@let one = value + 1;
@let two = one + 1;
<button (click)="callback(one, two)"></button>
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_in_listener.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
callback(one: number, two: number): void;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_in_child_view.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@if (true) {
@if (true) {
@let three = two + 1;
{{three}}
}
@let two = one + 1;
}
@let one = 1;
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
@if (true) {
@if (true) {
@let three = two + 1;
{{three}}
}
@let two = one + 1;
}
@let one = 1;
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_in_child_view.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_shared_with_child_view.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@let value = 123;
{{value}}
<ng-template>{{value}}</ng-template>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
@let value = 123;
{{value}}
<ng-template>{{value}}</ng-template>
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_shared_with_child_view.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_in_child_view_listener.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 1;
}
callback(one, two, three, four) {
console.log(one, two, three, four);
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@let one = value + 1;
<ng-template>
@let two = one + 1;
@if (true) {
@let three = two + 1;
@switch (1) {
@case (1) {
@let four = three + 1;
<button (click)="callback(one, two, three, four)"></button>
}
}
}
</ng-template>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
@let one = value + 1;
<ng-template>
@let two = one + 1;
@if (true) {
@let three = two + 1;
@switch (1) {
@case (1) {
@let four = three + 1;
<button (click)="callback(one, two, three, four)"></button>
}
}
}
</ng-template>
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_in_child_view_listener.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
callback(one: number, two: number, three: number, four: number): void;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_local_refs.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
<input #name>
<input #lastName>
@let fullName = name.value + ' ' + lastName.value;
Hello, {{fullName}}
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
<input #name>
<input #lastName>
@let fullName = name.value + ' ' + lastName.value;
Hello, {{fullName}}
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_local_refs.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_local_forward_refs.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@let message = 'Hello, ' + name.value;
{{message}}
<input #name>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
@let message = 'Hello, ' + name.value;
{{message}}
<input #name>
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_local_forward_refs.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_for_loop.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.items = [];
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@for (item of items; track item) {
@let outerFirst = $first;
@for (subitem of item.children; track subitem) {
@let innerFirst = $first;
{{outerFirst || innerFirst}}
}
}
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
@for (item of items; track item) {
@let outerFirst = $first;
@for (subitem of item.children; track subitem) {
@let innerFirst = $first;
{{outerFirst || innerFirst}}
}
}
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_for_loop.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
items: {
children: any[];
}[];
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_invalid_forward_ref.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 1;
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
<ng-template>
{{result}}
@let result = value * 2;
</ng-template>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
<ng-template>
{{result}}
@let result = value * 2;
</ng-template>
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_invalid_forward_ref.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_single_optimization.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 0;
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
{{value}}
@let result = value * 2;
{{value}}
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
{{value}}
@let result = value * 2;
{{value}}
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_single_optimization.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_multiple_optimization.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 0;
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{value}}
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{value}}
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_multiple_optimization.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_partial_optimization.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 0;
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{two}}
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{two}}
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_partial_optimization.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_optimization_listener.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 0;
}
callback(value) {
console.log(value);
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{value}}
<button (click)="callback(three)"></button>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{value}}
<button (click)="callback(three)"></button>
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_optimization_listener.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
callback(value: number): void;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: let_optimization_child_view.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.value = 0;
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{value}}
@if (true) {
{{three}}
}
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{value}}
@if (true) {
{{three}}
}
`,
standalone: true
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: let_optimization_child_view.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
value: number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}

View file

@ -0,0 +1,326 @@
{
"$schema": "../test_case_schema.json",
"cases": [
{
"description": "should create a simple @let declaration",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"simple_let.ts"
],
"expectations": [
{
"files": [
{
"expected": "simple_let_template.js",
"generated": "simple_let.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should create multiple @let declarations that depend on each other",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"multiple_let.ts"
],
"expectations": [
{
"files": [
{
"expected": "multiple_let_template.js",
"generated": "multiple_let.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should create a let using a pipe",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_with_pipe.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_with_pipe_template.js",
"generated": "let_with_pipe.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should be able to use let declarations in event listeners",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_in_listener.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_in_listener_template.js",
"generated": "let_in_listener.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should be able to use let declarations in child views",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_in_child_view.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_in_child_view_template.js",
"generated": "let_in_child_view.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should share let declarations between parent and child views",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_shared_with_child_view.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_shared_with_child_view_template.js",
"generated": "let_shared_with_child_view.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should be able to use let declarations in event listeners inside child views",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_in_child_view_listener.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_in_child_view_listener_template.js",
"generated": "let_in_child_view_listener.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should be able to use local references in let declarations",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_local_refs.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_local_refs_template.js",
"generated": "let_local_refs.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should be able to use forward references defined after the let declaration",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_local_forward_refs.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_local_forward_refs_template.js",
"generated": "let_local_forward_refs.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should be able to use for loop variables in let declarations",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_for_loop.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_for_loop_template.js",
"generated": "let_for_loop.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should transform an invalid let reference to undefined",
"angularCompilerOptions": {
"_enableLetSyntax": true,
"checkTemplateBodies": false
},
"inputFiles": [
"let_invalid_forward_ref.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_invalid_forward_ref_template.js",
"generated": "let_invalid_forward_ref.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should remove a single unused let declaration",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_single_optimization.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_single_optimization_template.js",
"generated": "let_single_optimization.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should remove a chain of unused let declarations",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_multiple_optimization.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_multiple_optimization_template.js",
"generated": "let_multiple_optimization.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should remove only the unused let declarations from the middle of a chain of declarations",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_partial_optimization.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_partial_optimization_template.js",
"generated": "let_partial_optimization.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should not remove let declarations that are only used in an event listener",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_optimization_listener.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_optimization_listener_template.js",
"generated": "let_optimization_listener.js"
}
],
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should not remove let declarations that are only used in a child view",
"angularCompilerOptions": {
"_enableLetSyntax": true
},
"inputFiles": [
"let_optimization_child_view.ts"
],
"expectations": [
{
"files": [
{
"expected": "let_optimization_child_view_template.js",
"generated": "let_optimization_child_view.js"
}
],
"failureMessage": "Incorrect template"
}
]
}
]
}

View file

@ -0,0 +1,19 @@
import {Component} from '@angular/core';
@Component({
template: `
@for (item of items; track item) {
@let outerFirst = $first;
@for (subitem of item.children; track subitem) {
@let innerFirst = $first;
{{outerFirst || innerFirst}}
}
}
`,
standalone: true,
})
export class MyApp {
items: {children: any[]}[] = [];
}

View file

@ -0,0 +1,47 @@
function MyApp_For_1_For_2_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵtext(1);
}
if (rf & 2) {
const ɵ$index_3_r1 = ctx.$index;
$r3$.ɵɵnextContext();
const $outerFirst_1$ = $r3$.ɵɵreadContextLet(0);
const $innerFirst_2$ = ɵ$index_3_r1 === 0;
$r3$.ɵɵadvance();
$r3$.ɵɵtextInterpolate1(" ", $outerFirst_1$ || $innerFirst_2$, " ");
}
}
function MyApp_For_1_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵrepeaterCreate(1, MyApp_For_1_For_2_Template, 2, 1, null, null, $r3$.ɵɵrepeaterTrackByIdentity);
}
if (rf & 2) {
const $item_r4$ = ctx.$implicit;
const ɵ$index_1_r5 = ctx.$index;
$r3$.ɵɵstoreLet(ɵ$index_1_r5 === 0);
$r3$.ɵɵadvance();
$r3$.ɵɵrepeater($item_r4$.children);
}
}
$r3$.ɵɵdefineComponent({
decls: 2,
vars: 0,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵrepeaterCreate(0, MyApp_For_1_Template, 3, 1, null, null, $r3$.ɵɵrepeaterTrackByIdentity);
}
if (rf & 2) {
$r3$.ɵɵrepeater(ctx.items);
}
},
});

View file

@ -0,0 +1,17 @@
import {Component} from '@angular/core';
@Component({
template: `
@if (true) {
@if (true) {
@let three = two + 1;
{{three}}
}
@let two = one + 1;
}
@let one = 1;
`,
standalone: true,
})
export class MyApp {}

View file

@ -0,0 +1,30 @@
import {Component} from '@angular/core';
@Component({
template: `
@let one = value + 1;
<ng-template>
@let two = one + 1;
@if (true) {
@let three = two + 1;
@switch (1) {
@case (1) {
@let four = three + 1;
<button (click)="callback(one, two, three, four)"></button>
}
}
}
</ng-template>
`,
standalone: true,
})
export class MyApp {
value = 1;
callback(one: number, two: number, three: number, four: number) {
console.log(one, two, three, four);
}
}

View file

@ -0,0 +1,79 @@
function MyApp_ng_template_1_Conditional_1_Case_1_Template(rf, ctx) {
if (rf & 1) {
const $_r1$ = $r3$.ɵɵgetCurrentView();
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵelementStart(1, "button", 0);
$r3$.ɵɵlistener(
"click",
function MyApp_ng_template_1_Conditional_1_Case_1_Template_button_click_1_listener() {
$r3$.ɵɵrestoreView($_r1$);
const $four_1$ = $r3$.ɵɵreadContextLet(0);
$r3$.ɵɵnextContext();
const $three_2$ = $r3$.ɵɵreadContextLet(0);
$r3$.ɵɵnextContext();
const $two_3$ = $r3$.ɵɵreadContextLet(0);
const $ctx_r4$ = $r3$.ɵɵnextContext();
const $one_5$ = $r3$.ɵɵreadContextLet(0);
return $r3$.ɵɵresetView($ctx_r4$.callback($one_5$, $two_3$, $three_2$, $four_1$));
}
);
$r3$.ɵɵelementEnd();
}
if (rf & 2) {
$r3$.ɵɵnextContext();
const $three_2$ = $r3$.ɵɵreadContextLet(0);
$r3$.ɵɵstoreLet($three_2$ + 1);
}
}
function MyApp_ng_template_1_Conditional_1_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵtemplate(1, MyApp_ng_template_1_Conditional_1_Case_1_Template, 2, 1, "button");
}
if (rf & 2) {
let $tmp_5_0$;
$r3$.ɵɵnextContext();
const $two_3$ = $r3$.ɵɵreadContextLet(0);
$r3$.ɵɵstoreLet($two_3$ + 1);
$r3$.ɵɵadvance();
$r3$.ɵɵconditional(($tmp_5_0$ = 1) === 1 ? 1 : -1);
}
}
function MyApp_ng_template_1_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵtemplate(1, MyApp_ng_template_1_Conditional_1_Template, 2, 2);
}
if (rf & 2) {
$r3$.ɵɵnextContext();
const $one_5$ = $r3$.ɵɵreadContextLet(0);
$r3$.ɵɵstoreLet($one_5$ + 1);
$r3$.ɵɵadvance();
$r3$.ɵɵconditional(true ? 1 : -1);
}
}
$r3$.ɵɵdefineComponent({
decls: 2,
vars: 1,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵtemplate(1, MyApp_ng_template_1_Template, 2, 2, "ng-template");
}
if (rf & 2) {
$r3$.ɵɵstoreLet(ctx.value + 1);
}
},
});

View file

@ -0,0 +1,49 @@
function MyApp_Conditional_0_Conditional_0_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵtext(1);
}
if (rf & 2) {
$r3$.ɵɵnextContext();
const $two_0$ = $r3$.ɵɵreadContextLet(1);
const $three_1$ = $two_0$ + 1;
$r3$.ɵɵadvance();
$r3$.ɵɵtextInterpolate1(" ", $three_1$, " ");
}
}
function MyApp_Conditional_0_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyApp_Conditional_0_Conditional_0_Template, 2, 1);
$r3$.ɵɵdeclareLet(1);
}
if (rf & 2) {
$r3$.ɵɵnextContext();
const $one_2$ = $r3$.ɵɵreadContextLet(1);
$r3$.ɵɵconditional(true ? 0 : -1);
$r3$.ɵɵadvance();
$r3$.ɵɵstoreLet($one_2$ + 1);
}
}
$r3$.ɵɵdefineComponent({
decls: 2,
vars: 2,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyApp_Conditional_0_Template, 2, 2);
$r3$.ɵɵdeclareLet(1);
}
if (rf & 2) {
$r3$.ɵɵconditional(true ? 0 : -1);
$r3$.ɵɵadvance();
$r3$.ɵɵstoreLet(1);
}
},
});

View file

@ -0,0 +1,18 @@
import {Component} from '@angular/core';
@Component({
template: `
@let one = value + 1;
@let two = one + 1;
<button (click)="callback(one, two)"></button>
`,
standalone: true,
})
export class MyApp {
value = 1;
callback(one: number, two: number) {
console.log(one, two);
}
}

View file

@ -0,0 +1,26 @@
$r3$.ɵɵdefineComponent({
decls: 3,
vars: 2,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
const $_r1$ = $r3$.ɵɵgetCurrentView();
$r3$.ɵɵdeclareLet(0)(1);
$r3$.ɵɵelementStart(2, "button", 0);
$r3$.ɵɵlistener("click", function MyApp_Template_button_click_2_listener() {
$r3$.ɵɵrestoreView($_r1$);
const $one_1$ = $r3$.ɵɵreadContextLet(0);
const $two_2$ = $r3$.ɵɵreadContextLet(1);
return $r3$.ɵɵresetView(ctx.callback($one_1$, $two_2$));
});
$r3$.ɵɵelementEnd();
}
if (rf & 2) {
const $one_r1$ = $r3$.ɵɵstoreLet(ctx.value + 1);
$r3$.ɵɵadvance();
$r3$.ɵɵstoreLet($one_r1$ + 1);
}
},
});

View file

@ -0,0 +1,14 @@
import {Component} from '@angular/core';
@Component({
template: `
<ng-template>
{{result}}
@let result = value * 2;
</ng-template>
`,
standalone: true,
})
export class MyApp {
value = 1;
}

View file

@ -0,0 +1,19 @@
function MyApp_ng_template_0_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtext(0);
$r3$.ɵɵdeclareLet(1);
}
if (rf & 2) {
const $ctx_r0$ = $r3$.ɵɵnextContext();
$r3$.ɵɵtextInterpolate1(" ", undefined, " ");
$ctx_r0$.value * 2;
}
}
function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyApp_ng_template_0_Template, 2, 1, "ng-template");
}
}

View file

@ -0,0 +1,11 @@
import {Component} from '@angular/core';
@Component({
template: `
@let message = 'Hello, ' + name.value;
{{message}}
<input #name>
`,
standalone: true,
})
export class MyApp {}

View file

@ -0,0 +1,20 @@
$r3$.ɵɵdefineComponent({
decls: 4,
vars: 1,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵtext(1);
$r3$.ɵɵelement(2, "input", null, 0);
}
if (rf & 2) {
const $name_r1$ = $r3$.ɵɵreference(3);
const $message_1$ = "Hello, " + $name_r1$.value;
$r3$.ɵɵadvance();
$r3$.ɵɵtextInterpolate1(" ", $message_1$, " ");
}
},
});

View file

@ -0,0 +1,13 @@
import {Component} from '@angular/core';
@Component({
template: `
<input #name>
<input #lastName>
@let fullName = name.value + ' ' + lastName.value;
Hello, {{fullName}}
`,
standalone: true,
})
export class MyApp {}

View file

@ -0,0 +1,21 @@
$r3$.ɵɵdefineComponent({
decls: 6,
vars: 1,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "input", null, 0)(2, "input", null, 1);
$r3$.ɵɵdeclareLet(4);
$r3$.ɵɵtext(5);
}
if (rf & 2) {
const $name_r1$ = $r3$.ɵɵreference(1);
const $lastName_r2$ = $r3$.ɵɵreference(3);
const $fullName_2$ = $name_r1$.value + " " + $lastName_r2$.value;
$r3$.ɵɵadvance(5);
$r3$.ɵɵtextInterpolate1(" Hello, ", $fullName_2$, " ");
}
},
});

View file

@ -0,0 +1,16 @@
import {Component} from '@angular/core';
@Component({
template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{value}}
`,
standalone: true,
})
export class MyApp {
value = 0;
}

View file

@ -0,0 +1,22 @@
$r3$.ɵɵdefineComponent({
decls: 6,
vars: 2,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtext(0);
$r3$.ɵɵdeclareLet(1)(2)(3)(4);
$r3$.ɵɵtext(5);
}
if (rf & 2) {
$r3$.ɵɵtextInterpolate1(" ", ctx.value, " ");
const $one_1$ = ctx.value + 1;
const $two_2$ = $one_1$ + 1;
const $three_3$ = $two_2$ + 1;
$three_3$ + 1;
$r3$.ɵɵadvance(5);
$r3$.ɵɵtextInterpolate1(" ", ctx.value, " ");
}
},
});

View file

@ -0,0 +1,19 @@
import {Component} from '@angular/core';
@Component({
template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{value}}
@if (true) {
{{three}}
}
`,
standalone: true,
})
export class MyApp {
value = 0;
}

View file

@ -0,0 +1,39 @@
function MyApp_Conditional_6_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtext(0);
}
if (rf & 2) {
$r3$.ɵɵnextContext();
const $three_0$ = $r3$.ɵɵreadContextLet(3);
$r3$.ɵɵtextInterpolate1(" ", $three_0$, " ");
}
}
$r3$.ɵɵdefineComponent({
decls: 7,
vars: 4,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtext(0);
$r3$.ɵɵdeclareLet(1)(2)(3)(4);
$r3$.ɵɵtext(5);
$r3$.ɵɵtemplate(6, MyApp_Conditional_6_Template, 1, 1);
}
if (rf & 2) {
$r3$.ɵɵtextInterpolate1(" ", ctx.value, " ");
const $one_1$ = ctx.value + 1;
const $two_2$ = $one_1$ + 1;
$r3$.ɵɵadvance(3);
const $three_3$ = i0.ɵɵstoreLet($two_2$ + 1);
$three_3$ + 1;
$r3$.ɵɵadvance(2);
$r3$.ɵɵtextInterpolate1(" ", ctx.value, " ");
$r3$.ɵɵadvance();
$r3$.ɵɵconditional(true ? 6 : -1);
}
},
});

View file

@ -0,0 +1,21 @@
import {Component} from '@angular/core';
@Component({
template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{value}}
<button (click)="callback(three)"></button>
`,
standalone: true,
})
export class MyApp {
value = 0;
callback(value: number) {
console.log(value);
}
}

View file

@ -0,0 +1,32 @@
$r3$.ɵɵdefineComponent({
decls: 7,
vars: 3,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
const $_r1$ = $r3$.ɵɵgetCurrentView();
$r3$.ɵɵtext(0);
$r3$.ɵɵdeclareLet(1)(2)(3)(4);
$r3$.ɵɵtext(5);
$r3$.ɵɵelementStart(6, "button", 0);
$r3$.ɵɵlistener("click", function MyApp_Template_button_click_6_listener() {
$r3$.ɵɵrestoreView($_r1$);
const $three_1$ = $r3$.ɵɵreadContextLet(3);
return $r3$.ɵɵresetView(ctx.callback($three_1$));
});
$r3$.ɵɵelementEnd();
}
if (rf & 2) {
$r3$.ɵɵtextInterpolate1(" ", ctx.value, " ");
const $one_2$ = ctx.value + 1;
const $two_3$ = $one_2$ + 1;
$r3$.ɵɵadvance(3);
const $three_5$ = $r3$.ɵɵstoreLet($two_3$ + 1);
$three_5$ + 1;
$r3$.ɵɵadvance(2);
$r3$.ɵɵtextInterpolate1(" ", ctx.value, " ");
}
},
});

View file

@ -0,0 +1,16 @@
import {Component} from '@angular/core';
@Component({
template: `
{{value}}
@let one = value + 1;
@let two = one + 1;
@let three = two + 1;
@let four = three + 1;
{{two}}
`,
standalone: true,
})
export class MyApp {
value = 0;
}

View file

@ -0,0 +1,22 @@
$r3$.ɵɵdefineComponent({
decls: 6,
vars: 2,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtext(0);
$r3$.ɵɵdeclareLet(1)(2)(3)(4);
$r3$.ɵɵtext(5);
}
if (rf & 2) {
$r3$.ɵɵtextInterpolate1(" ", ctx.value, " ");
const $one_0$ = ctx.value + 1;
const $two_1$ = $one_0$ + 1;
const $three_2$ = $two_1$ + 1;
$three_2$ + 1;
$r3$.ɵɵadvance(5);
$r3$.ɵɵtextInterpolate1(" ", $two_1$, " ");
}
},
});

View file

@ -0,0 +1,11 @@
import {Component} from '@angular/core';
@Component({
template: `
@let value = 123;
{{value}}
<ng-template>{{value}}</ng-template>
`,
standalone: true,
})
export class MyApp {}

View file

@ -0,0 +1,31 @@
function MyApp_ng_template_2_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtext(0);
}
if (rf & 2) {
$r3$.ɵɵnextContext();
const $value_0$ = $r3$.ɵɵreadContextLet(0);
$r3$.ɵɵtextInterpolate($value_0$);
}
}
$r3$.ɵɵdefineComponent({
decls: 3,
vars: 2,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵtext(1);
$r3$.ɵɵtemplate(2, MyApp_ng_template_2_Template, 1, 1, "ng-template");
}
if (rf & 2) {
const $value_r0$ = $r3$.ɵɵstoreLet(123);
$r3$.ɵɵadvance();
$r3$.ɵɵtextInterpolate1(" ", $value_r0$, " ");
}
},
});

View file

@ -0,0 +1,13 @@
import {Component} from '@angular/core';
@Component({
template: `
{{value}}
@let result = value * 2;
{{value}}
`,
standalone: true,
})
export class MyApp {
value = 0;
}

View file

@ -0,0 +1,19 @@
$r3$.ɵɵdefineComponent({
decls: 3,
vars: 2,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtext(0);
$r3$.ɵɵdeclareLet(1);
$r3$.ɵɵtext(2);
}
if (rf & 2) {
$r3$.ɵɵtextInterpolate1(" ", ctx.value, " ");
ctx.value * 2;
$r3$.ɵɵadvance(2);
$r3$.ɵɵtextInterpolate1(" ", ctx.value, " ");
}
},
});

View file

@ -0,0 +1,24 @@
import {Component, Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'double',
standalone: true,
})
export class DoublePipe implements PipeTransform {
transform(value: number) {
return value * 2;
}
}
@Component({
template: `
@let one = value + 1;
@let result = one | double;
The result is {{result}}
`,
standalone: true,
imports: [DoublePipe],
})
export class MyApp {
value = 1;
}

View file

@ -0,0 +1,19 @@
$r3$.ɵɵdefineComponent({
decls: 4,
vars: 3,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0)(1);
$r3$.ɵɵpipe(2, "double");
$r3$.ɵɵtext(3);
}
if (rf & 2) {
const $one_0$ = ctx.value + 1;
const $result_1$ = $r3$.ɵɵpipeBind1(2, 1, $one_0$);
$r3$.ɵɵadvance(3);
$r3$.ɵɵtextInterpolate1(" The result is ", $result_1$, " ");
}
},
});

View file

@ -0,0 +1,14 @@
import {Component} from '@angular/core';
@Component({
template: `
@let one = value + 1;
@let two = one + 1;
@let result = two + 1;
The result is {{result}}
`,
standalone: true,
})
export class MyApp {
value = 1;
}

View file

@ -0,0 +1,19 @@
$r3$.ɵɵdefineComponent({
decls: 4,
vars: 1,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0)(1)(2);
$r3$.ɵɵtext(3);
}
if (rf & 2) {
const $one_0$ = ctx.value + 1;
const $two_1$ = $one_0$ + 1;
const $result_2$ = $two_1$ + 1;
$r3$.ɵɵadvance(3);
$r3$.ɵɵtextInterpolate1(" The result is ", $result_2$, " ");
}
},
});

View file

@ -0,0 +1,12 @@
import {Component} from '@angular/core';
@Component({
template: `
@let result = value * 2;
The result is {{result}}
`,
standalone: true,
})
export class MyApp {
value = 1;
}

View file

@ -0,0 +1,17 @@
$r3$.ɵɵdefineComponent({
decls: 2,
vars: 1,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵdeclareLet(0);
$r3$.ɵɵtext(1);
}
if (rf & 2) {
const $result_0$ = ctx.value * 2;
$r3$.ɵɵadvance();
$r3$.ɵɵtextInterpolate1(" The result is ", $result_0$, " ");
}
},
});

View file

@ -495,6 +495,10 @@ export class Identifiers {
static twoWayBindingSet: o.ExternalReference = {name: 'ɵɵtwoWayBindingSet', moduleName: CORE};
static twoWayListener: o.ExternalReference = {name: 'ɵɵtwoWayListener', moduleName: CORE};
static declareLet: o.ExternalReference = {name: 'ɵɵdeclareLet', moduleName: CORE};
static storeLet: o.ExternalReference = {name: 'ɵɵstoreLet', moduleName: CORE};
static readContextLet: o.ExternalReference = {name: 'ɵɵreadContextLet', moduleName: CORE};
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵɵNgOnChangesFeature', moduleName: CORE};
static InheritDefinitionFeature: o.ExternalReference = {

View file

@ -205,6 +205,16 @@ export enum OpKind {
*/
TwoWayListener,
/**
* A creation-time operation that initializes the slot for a `@let` declaration.
*/
DeclareLet,
/**
* An update-time operation that stores the current value of a `@let` declaration.
*/
StoreLet,
/**
* The start of an i18n block.
*/
@ -290,6 +300,16 @@ export enum ExpressionKind {
*/
Reference,
/**
* A call storing the value of a `@let` declaration.
*/
StoreLet,
/**
* A reference to a `@let` declaration read from the context view.
*/
ContextLetReference,
/**
* Runtime operation to snapshot the current view context.
*/

View file

@ -15,7 +15,13 @@ import {SlotHandle} from './handle';
import type {XrefId} from './operations';
import type {CreateOp} from './ops/create';
import {Interpolation, type UpdateOp} from './ops/update';
import {ConsumesVarsTrait, UsesVarOffset, UsesVarOffsetTrait} from './traits';
import {
ConsumesVarsTrait,
DependsOnSlotContext,
DependsOnSlotContextOpTrait,
UsesVarOffset,
UsesVarOffsetTrait,
} from './traits';
/**
* An `o.Expression` subtype representing a logical expression in the intermediate representation.
@ -42,7 +48,9 @@ export type Expression =
| SlotLiteralExpr
| ConditionalCaseExpr
| ConstCollectedExpr
| TwoWayBindingSetExpr;
| TwoWayBindingSetExpr
| ContextLetReferenceExpr
| StoreLetExpr;
/**
* Transformer type which converts expressions into general `o.Expression`s (which may be an
@ -138,6 +146,73 @@ export class ReferenceExpr extends ExpressionBase {
}
}
export class StoreLetExpr
extends ExpressionBase
implements ConsumesVarsTrait, DependsOnSlotContextOpTrait
{
override readonly kind = ExpressionKind.StoreLet;
readonly [ConsumesVarsTrait] = true;
readonly [DependsOnSlotContext] = true;
constructor(
readonly target: XrefId,
public value: o.Expression,
override sourceSpan: ParseSourceSpan,
) {
super();
}
override visitExpression(): void {}
override isEquivalent(e: o.Expression): boolean {
return (
e instanceof StoreLetExpr && e.target === this.target && e.value.isEquivalent(this.value)
);
}
override isConstant(): boolean {
return false;
}
override transformInternalExpressions(
transform: ExpressionTransform,
flags: VisitorContextFlag,
): void {
this.value = transformExpressionsInExpression(this.value, transform, flags);
}
override clone(): StoreLetExpr {
return new StoreLetExpr(this.target, this.value, this.sourceSpan);
}
}
export class ContextLetReferenceExpr extends ExpressionBase {
override readonly kind = ExpressionKind.ContextLetReference;
constructor(
readonly target: XrefId,
readonly targetSlot: SlotHandle,
) {
super();
}
override visitExpression(): void {}
override isEquivalent(e: o.Expression): boolean {
return e instanceof ContextLetReferenceExpr && e.target === this.target;
}
override isConstant(): boolean {
return false;
}
override transformInternalExpressions(): void {}
override clone(): ContextLetReferenceExpr {
return new ContextLetReferenceExpr(this.target, this.targetSlot);
}
}
/**
* A reference to the current view context (usually the `ctx` variable in a template function).
*/
@ -1115,6 +1190,9 @@ export function transformExpressionsInOp(
case OpKind.DeferWhen:
op.expr = transformExpressionsInExpression(op.expr, transform, flags);
break;
case OpKind.StoreLet:
op.value = transformExpressionsInExpression(op.value, transform, flags);
break;
case OpKind.Advance:
case OpKind.Container:
case OpKind.ContainerEnd:
@ -1140,6 +1218,7 @@ export function transformExpressionsInOp(
case OpKind.Text:
case OpKind.I18nAttributes:
case OpKind.IcuPlaceholder:
case OpKind.DeclareLet:
// These operations contain no expressions.
break;
default:

View file

@ -67,7 +67,8 @@ export type CreateOp =
| IcuEndOp
| IcuPlaceholderOp
| I18nContextOp
| I18nAttributesOp;
| I18nAttributesOp
| DeclareLetOp;
/**
* An operation representing the creation of an element or container.
@ -1072,6 +1073,35 @@ export function createDeferOnOp(
};
}
/**
* Op that reserves a slot during creation time for a `@let` declaration.
*/
export interface DeclareLetOp extends Op<CreateOp>, ConsumesSlotOpTrait {
kind: OpKind.DeclareLet;
xref: XrefId;
sourceSpan: ParseSourceSpan;
declaredName: string;
}
/**
* Creates a `DeclareLetOp`.
*/
export function createDeclareLetOp(
xref: XrefId,
declaredName: string,
sourceSpan: ParseSourceSpan,
): DeclareLetOp {
return {
kind: OpKind.DeclareLet,
xref,
declaredName,
sourceSpan,
handle: new SlotHandle(),
...TRAIT_CONSUMES_SLOT,
...NEW_OP,
};
}
/**
* Represents a single value in an i18n param map. Each placeholder in the map may have multiple of
* these values associated with it.

View file

@ -51,7 +51,8 @@ export type UpdateOp =
| I18nExpressionOp
| I18nApplyOp
| RepeaterOp
| DeferWhenOp;
| DeferWhenOp
| StoreLetOp;
/**
* A logical operation to perform string interpolation on a text node.
@ -949,3 +950,41 @@ export function createI18nApplyOp(
...NEW_OP,
};
}
/**
* Op to store the current value of a `@let` declaration.
*/
export interface StoreLetOp extends Op<UpdateOp>, ConsumesVarsTrait {
kind: OpKind.StoreLet;
sourceSpan: ParseSourceSpan;
/** Name that the user set when declaring the `@let`. */
declaredName: string;
/** XrefId of the slot in which the call may write its value. */
target: XrefId;
/** Value of the `@let` declaration. */
value: o.Expression;
}
/**
* Creates a `StoreLetOp`.
*/
export function createStoreLetOp(
target: XrefId,
declaredName: string,
value: o.Expression,
sourceSpan: ParseSourceSpan,
): StoreLetOp {
return {
kind: OpKind.StoreLet,
target,
declaredName,
value,
sourceSpan,
...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
...TRAIT_CONSUMES_VARS,
...NEW_OP,
};
}

View file

@ -8,6 +8,7 @@
import type {ParseSourceSpan} from '../../../../parse_util';
import type {Expression} from './expression';
import * as o from '../../../../output/output_ast';
import type {Op, XrefId} from './operations';
import {SlotHandle} from './handle';
@ -135,10 +136,14 @@ export function hasConsumesSlotTrait<OpT extends Op<OpT>>(
/**
* Test whether an operation implements `DependsOnSlotContextOpTrait`.
*/
export function hasDependsOnSlotContextTrait<ExprT extends o.Expression>(
expr: ExprT,
): expr is ExprT & DependsOnSlotContextOpTrait;
export function hasDependsOnSlotContextTrait<OpT extends Op<OpT>>(
op: OpT,
): op is OpT & DependsOnSlotContextOpTrait {
return (op as Partial<DependsOnSlotContextOpTrait>)[DependsOnSlotContext] === true;
): op is OpT & DependsOnSlotContextOpTrait;
export function hasDependsOnSlotContextTrait(value: any): boolean {
return (value as Partial<DependsOnSlotContextOpTrait>)[DependsOnSlotContext] === true;
}
/**

View file

@ -80,6 +80,9 @@ import {generateTrackVariables} from './phases/track_variables';
import {countVariables} from './phases/var_counting';
import {optimizeVariables} from './phases/variable_optimization';
import {wrapI18nIcus} from './phases/wrap_icus';
import {optimizeStoreLet} from './phases/store_let_optimization';
import {removeIllegalLetReferences} from './phases/remove_illegal_let_references';
import {generateLocalLetReferences} from './phases/generate_local_let_references';
type Phase =
| {
@ -121,11 +124,13 @@ const phases: Phase[] = [
{kind: Kind.Tmpl, fn: createVariadicPipes},
{kind: Kind.Both, fn: generatePureLiteralStructures},
{kind: Kind.Tmpl, fn: generateProjectionDefs},
{kind: Kind.Tmpl, fn: generateLocalLetReferences},
{kind: Kind.Tmpl, fn: generateVariables},
{kind: Kind.Tmpl, fn: saveAndRestoreView},
{kind: Kind.Both, fn: deleteAnyCasts},
{kind: Kind.Both, fn: resolveDollarEvent},
{kind: Kind.Tmpl, fn: generateTrackVariables},
{kind: Kind.Tmpl, fn: removeIllegalLetReferences},
{kind: Kind.Both, fn: resolveNames},
{kind: Kind.Tmpl, fn: resolveDeferTargetNames},
{kind: Kind.Tmpl, fn: transformTwoWayBindingSet},
@ -137,6 +142,7 @@ const phases: Phase[] = [
{kind: Kind.Both, fn: expandSafeReads},
{kind: Kind.Both, fn: generateTemporaryVariables},
{kind: Kind.Both, fn: optimizeVariables},
{kind: Kind.Both, fn: optimizeStoreLet},
{kind: Kind.Tmpl, fn: allocateSlots},
{kind: Kind.Tmpl, fn: resolveI18nElementPlaceholders},
{kind: Kind.Tmpl, fn: resolveI18nExpressionPlaceholders},

View file

@ -226,7 +226,7 @@ function ingestNodes(unit: ViewCompilationUnit, template: t.Node[]): void {
} else if (node instanceof t.ForLoopBlock) {
ingestForBlock(unit, node);
} else if (node instanceof t.LetDeclaration) {
// TODO(crisbeto): needs further integration
ingestLetDeclaration(unit, node);
} else {
throw new Error(`Unsupported template node: ${node.constructor.name}`);
}
@ -926,6 +926,20 @@ function getComputedForLoopVariableExpression(
}
}
function ingestLetDeclaration(unit: ViewCompilationUnit, node: t.LetDeclaration) {
const target = unit.job.allocateXrefId();
unit.create.push(ir.createDeclareLetOp(target, node.name, node.sourceSpan));
unit.update.push(
ir.createStoreLetOp(
target,
node.name,
convertAst(node.value, unit.job, node.valueSpan),
node.sourceSpan,
),
);
}
/**
* Convert a template AST expression into an output AST expression.
*/

View file

@ -401,6 +401,18 @@ export function deferWhen(
return call(prefetch ? Identifiers.deferPrefetchWhen : Identifiers.deferWhen, [expr], sourceSpan);
}
export function declareLet(slot: number, sourceSpan: ParseSourceSpan): ir.CreateOp {
return call(Identifiers.declareLet, [o.literal(slot)], sourceSpan);
}
export function storeLet(value: o.Expression, sourceSpan: ParseSourceSpan): o.Expression {
return o.importExpr(Identifiers.storeLet).callFn([value], sourceSpan);
}
export function readContextLet(slot: number): o.Expression {
return o.importExpr(Identifiers.readContextLet).callFn([o.literal(slot)]);
}
export function i18n(
slot: number,
constIndex: number,

View file

@ -40,6 +40,7 @@ const CHAINABLE = new Set([
R3.templateCreate,
R3.twoWayProperty,
R3.twoWayListener,
R3.declareLet,
]);
/**

View file

@ -36,16 +36,29 @@ export function generateAdvance(job: CompilationJob): void {
// To do that, we track what the runtime's slot counter will be through the update operations.
let slotContext = 0;
for (const op of unit.update) {
if (!ir.hasDependsOnSlotContextTrait(op)) {
// `op` doesn't depend on the slot counter, so it can be skipped.
continue;
} else if (!slotMap.has(op.target)) {
// We expect ops that _do_ depend on the slot counter to point at declarations that exist in
// the `slotMap`.
throw new Error(`AssertionError: reference to unknown slot for target ${op.target}`);
let consumer: ir.DependsOnSlotContextOpTrait | null = null;
if (ir.hasDependsOnSlotContextTrait(op)) {
consumer = op;
} else {
ir.visitExpressionsInOp(op, (expr) => {
if (consumer === null && ir.hasDependsOnSlotContextTrait(expr)) {
consumer = expr;
}
});
}
const slot = slotMap.get(op.target)!;
if (consumer === null) {
continue;
}
if (!slotMap.has(consumer.target)) {
// We expect ops that _do_ depend on the slot counter to point at declarations that exist in
// the `slotMap`.
throw new Error(`AssertionError: reference to unknown slot for target ${consumer.target}`);
}
const slot = slotMap.get(consumer.target)!;
// Does the slot counter need to be adjusted?
if (slotContext !== slot) {
@ -55,10 +68,7 @@ export function generateAdvance(job: CompilationJob): void {
throw new Error(`AssertionError: slot counter should never need to move backwards`);
}
ir.OpList.insertBefore<ir.UpdateOp>(
ir.createAdvanceOp(delta, (op as ir.DependsOnSlotContextOpTrait).sourceSpan),
op,
);
ir.OpList.insertBefore<ir.UpdateOp>(ir.createAdvanceOp(delta, consumer.sourceSpan), op);
slotContext = slot;
}
}

View file

@ -0,0 +1,41 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ir from '../../ir';
import type {ComponentCompilationJob} from '../compilation';
/**
* Replaces the `storeLet` ops with variables that can be
* used to reference the value within the same view.
*/
export function generateLocalLetReferences(job: ComponentCompilationJob): void {
for (const unit of job.units) {
for (const op of unit.update) {
if (op.kind !== ir.OpKind.StoreLet) {
continue;
}
const variable: ir.IdentifierVariable = {
kind: ir.SemanticVariableKind.Identifier,
name: null,
identifier: op.declaredName,
};
ir.OpList.replace<ir.UpdateOp>(
op,
ir.createVariableOp<ir.UpdateOp>(
job.allocateXrefId(),
variable,
new ir.StoreLetExpr(op.target, op.value, op.sourceSpan),
ir.VariableFlags.None,
),
);
}
}
}

View file

@ -61,14 +61,12 @@ function recursivelyProcessView(view: ViewCompilationUnit, parentScope: Scope |
case ir.OpKind.Listener:
case ir.OpKind.TwoWayListener:
// Prepend variables to listener handler functions.
op.handlerOps.prepend(generateVariablesInScopeForView(view, scope));
op.handlerOps.prepend(generateVariablesInScopeForView(view, scope, true));
break;
}
}
// Prepend the declarations for all available variables in scope to the `update` block.
const preambleOps = generateVariablesInScopeForView(view, scope);
view.update.prepend(preambleOps);
view.update.prepend(generateVariablesInScopeForView(view, scope, false));
}
/**
@ -91,6 +89,11 @@ interface Scope {
*/
references: Reference[];
/**
* `@let` declarations collected from the view.
*/
letDeclarations: LetDeclaration[];
/**
* `Scope` of the parent view, if any.
*/
@ -126,6 +129,20 @@ interface Reference {
variable: ir.SemanticVariable;
}
/**
* Information about `@let` declaration collected from a view.
*/
interface LetDeclaration {
/** `XrefId` of the `@let` declaration that the reference is pointing to. */
targetId: ir.XrefId;
/** Slot in which the declaration is stored. */
targetSlot: ir.SlotHandle;
/** Variable referring to the declaration. */
variable: ir.IdentifierVariable;
}
/**
* Process a view and generate a `Scope` representing the variables available for reference within
* that view.
@ -141,6 +158,7 @@ function getScopeForView(view: ViewCompilationUnit, parent: Scope | null): Scope
contextVariables: new Map<string, ir.SemanticVariable>(),
aliases: view.aliases,
references: [],
letDeclarations: [],
parent,
};
@ -175,6 +193,18 @@ function getScopeForView(view: ViewCompilationUnit, parent: Scope | null): Scope
});
}
break;
case ir.OpKind.DeclareLet:
scope.letDeclarations.push({
targetId: op.xref,
targetSlot: op.handle,
variable: {
kind: ir.SemanticVariableKind.Identifier,
name: null,
identifier: op.declaredName,
},
});
break;
}
}
@ -190,6 +220,7 @@ function getScopeForView(view: ViewCompilationUnit, parent: Scope | null): Scope
function generateVariablesInScopeForView(
view: ViewCompilationUnit,
scope: Scope,
isListener: boolean,
): ir.VariableOp<ir.UpdateOp>[] {
const newOps: ir.VariableOp<ir.UpdateOp>[] = [];
@ -247,9 +278,22 @@ function generateVariablesInScopeForView(
);
}
if (scope.view !== view.xref || isListener) {
for (const decl of scope.letDeclarations) {
newOps.push(
ir.createVariableOp<ir.UpdateOp>(
view.job.allocateXrefId(),
decl.variable,
new ir.ContextLetReferenceExpr(decl.targetId, decl.targetSlot),
ir.VariableFlags.None,
),
);
}
}
if (scope.parent !== null) {
// Recursively add variables from the parent scope.
newOps.push(...generateVariablesInScopeForView(view, scope.parent));
newOps.push(...generateVariablesInScopeForView(view, scope.parent, false));
}
return newOps;
}

View file

@ -78,6 +78,7 @@ function mergeNextContextsInOps(ops: ir.OpList<ir.UpdateOp>): void {
break;
case ir.ExpressionKind.GetCurrentView:
case ir.ExpressionKind.Reference:
case ir.ExpressionKind.ContextLetReference:
// Can't merge past a dependency on the context.
tryToMerge = false;
break;

View file

@ -184,6 +184,9 @@ function reifyCreateOperations(unit: CompilationUnit, ops: ir.OpList<ir.CreateOp
case ir.OpKind.Pipe:
ir.OpList.replace(op, ng.pipe(op.handle.slot!, op.name));
break;
case ir.OpKind.DeclareLet:
ir.OpList.replace(op, ng.declareLet(op.handle.slot!, op.sourceSpan));
break;
case ir.OpKind.Listener:
const listenerFn = reifyListenerHandler(
unit,
@ -537,6 +540,8 @@ function reifyUpdateOperations(_unit: CompilationUnit, ops: ir.OpList<ir.UpdateO
case ir.OpKind.DeferWhen:
ir.OpList.replace(op, ng.deferWhen(op.prefetch, op.expr, op.sourceSpan));
break;
case ir.OpKind.StoreLet:
throw new Error(`AssertionError: unexpected storeLet ${op.declaredName}`);
case ir.OpKind.Statement:
// Pass statement operations directly through.
break;
@ -599,6 +604,10 @@ function reifyIrExpression(expr: o.Expression): o.Expression {
return ng.pipeBindV(expr.targetSlot.slot!, expr.varOffset!, expr.args);
case ir.ExpressionKind.SlotLiteralExpr:
return o.literal(expr.slot.slot!);
case ir.ExpressionKind.ContextLetReference:
return ng.readContextLet(expr.targetSlot.slot!);
case ir.ExpressionKind.StoreLet:
return ng.storeLet(expr.value, expr.sourceSpan);
default:
throw new Error(
`AssertionError: Unsupported reification of ir.Expression kind: ${

View file

@ -0,0 +1,46 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as o from '../../../../output/output_ast';
import * as ir from '../../ir';
import {CompilationJob} from '../compilation';
/**
* It's not allowed to access a `@let` declaration before it has been defined. This is enforced
* already via template type checking, however it can trip some of the assertions in the pipeline.
* E.g. the naming phase can fail because we resolved the variable here, but the variable doesn't
* exist anymore because the optimization phase removed it since it's invalid. To avoid surfacing
* confusing errors to users in the case where template type checking isn't running (e.g. in JIT
* mode) this phase detects illegal forward references and replaces them with `undefined`.
* Eventually users will see the proper error from the template type checker.
*/
export function removeIllegalLetReferences(job: CompilationJob): void {
for (const unit of job.units) {
for (const op of unit.update) {
if (
op.kind !== ir.OpKind.Variable ||
op.variable.kind !== ir.SemanticVariableKind.Identifier ||
!(op.initializer instanceof ir.StoreLetExpr)
) {
continue;
}
const name = op.variable.identifier;
let current: ir.UpdateOp | null = op;
while (current && current.kind !== ir.OpKind.ListEnd) {
ir.transformExpressionsInOp(
current,
(expr) =>
expr instanceof ir.LexicalReadExpr && expr.name === name ? o.literal(undefined) : expr,
ir.VisitorContextFlag.None,
);
current = current.prev;
}
}
}
}

View file

@ -8,7 +8,7 @@
import * as o from '../../../../output/output_ast';
import * as ir from '../../ir';
import {CompilationJob, CompilationUnit, ViewCompilationUnit} from '../compilation';
import {CompilationJob, CompilationUnit} from '../compilation';
/**
* Resolves lexical references in views (`ir.LexicalReadExpr`) to either a target variable or to

View file

@ -41,7 +41,7 @@ export function saveAndRestoreView(job: ComponentCompilationJob): void {
if (!needsRestoreView) {
for (const handlerOp of op.handlerOps) {
ir.visitExpressionsInOp(handlerOp, (expr) => {
if (expr instanceof ir.ReferenceExpr) {
if (expr instanceof ir.ReferenceExpr || expr instanceof ir.ContextLetReferenceExpr) {
// Listeners that reference() a local ref need the save/restore view operation.
needsRestoreView = true;
}

View file

@ -0,0 +1,45 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as o from '../../../../output/output_ast';
import * as ir from '../../ir';
import {CompilationJob} from '../compilation';
/**
* Removes any `storeLet` calls that aren't referenced outside of the current view.
*/
export function optimizeStoreLet(job: CompilationJob): void {
const letUsedExternally = new Set<ir.XrefId>();
// Since `@let` declarations can be referenced in child views, both in
// the creation block (via listeners) and in the update block, we have
// to look through all the ops to find the references.
for (const unit of job.units) {
for (const op of unit.ops()) {
ir.visitExpressionsInOp(op, (expr) => {
if (expr instanceof ir.ContextLetReferenceExpr) {
letUsedExternally.add(expr.target);
}
});
}
}
// TODO(crisbeto): potentially remove the unused calls completely, pending discussion.
for (const unit of job.units) {
for (const op of unit.update) {
ir.transformExpressionsInOp(
op,
(expression) =>
expression instanceof ir.StoreLetExpr && !letUsedExternally.has(expression.target)
? expression.value
: expression,
ir.VisitorContextFlag.None,
);
}
}
}

View file

@ -134,6 +134,7 @@ function varsUsedByOp(op: (ir.CreateOp | ir.UpdateOp) & ir.ConsumesVarsTrait): n
case ir.OpKind.I18nExpression:
case ir.OpKind.Conditional:
case ir.OpKind.DeferWhen:
case ir.OpKind.StoreLet:
return 1;
case ir.OpKind.RepeaterCreate:
// Repeaters may require an extra variable binding slot, if they have an empty view, for the
@ -154,6 +155,8 @@ export function varsUsedByIrExpression(expr: ir.Expression & ir.ConsumesVarsTrai
return 1 + expr.args.length;
case ir.ExpressionKind.PipeBindingVariadic:
return 1 + expr.numArgs;
case ir.ExpressionKind.StoreLet:
return 1;
default:
throw new Error(
`AssertionError: unhandled ConsumesVarsTrait expression ${expr.constructor.name}`,

View file

@ -320,7 +320,10 @@ function fencesForIrExpression(expr: ir.Expression): Fence {
return Fence.ViewContextRead | Fence.ViewContextWrite;
case ir.ExpressionKind.RestoreView:
return Fence.ViewContextRead | Fence.ViewContextWrite | Fence.SideEffectful;
case ir.ExpressionKind.StoreLet:
return Fence.SideEffectful;
case ir.ExpressionKind.Reference:
case ir.ExpressionKind.ContextLetReference:
return Fence.ViewContextRead;
default:
return Fence.None;

View file

@ -237,6 +237,9 @@ export {
ɵsetUnknownElementStrictMode,
ɵgetUnknownPropertyStrictMode,
ɵsetUnknownPropertyStrictMode,
ɵɵdeclareLet,
ɵɵstoreLet,
ɵɵreadContextLet,
} from './render3/index';
export {CONTAINER_HEADER_OFFSET as ɵCONTAINER_HEADER_OFFSET} from './render3/interfaces/container';
export {LContext as ɵLContext} from './render3/interfaces/context';

View file

@ -169,6 +169,9 @@ export {
ɵsetUnknownElementStrictMode,
ɵgetUnknownPropertyStrictMode,
ɵsetUnknownPropertyStrictMode,
ɵɵdeclareLet,
ɵɵstoreLet,
ɵɵreadContextLet,
} from './instructions/all';
export {
DEFER_BLOCK_DEPENDENCY_INTERCEPTOR as ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR,

View file

@ -62,3 +62,4 @@ export * from './template';
export * from './text';
export * from './text_interpolation';
export * from './two_way';
export * from './let_declaration';

View file

@ -0,0 +1,41 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Declares an `@let` at a specific data slot.
*
* @param index Index at which to declare the `@let`.
*
* @codeGenApi
*/
export function ɵɵdeclareLet(index: number): typeof ɵɵdeclareLet {
// TODO(crisbeto): implement this
return ɵɵdeclareLet;
}
/**
* Instruction that stores the value of a `@let` declaration on the current view.
*
* @codeGenApi
*/
export function ɵɵstoreLet<T>(value: T): T {
// TODO(crisbeto): implement this
return value;
}
/**
* Retrieves the value of a `@let` declaration defined within the same view.
*
* @param index Index of the declaration within the view.
*
* @codeGenApi
*/
export function ɵɵreadContextLet<T>(index: number): T {
// TODO(crisbeto): implement this
return null as any;
}

View file

@ -187,6 +187,9 @@ export const angularCoreEnv: {[name: string]: unknown} = (() => ({
'ɵɵregisterNgModuleType': registerNgModuleType,
'ɵɵgetComponentDepsFactory': r3.ɵɵgetComponentDepsFactory,
'ɵsetClassDebugInfo': r3.ɵsetClassDebugInfo,
'ɵɵdeclareLet': r3.ɵɵdeclareLet,
'ɵɵstoreLet': r3.ɵɵstoreLet,
'ɵɵreadContextLet': r3.ɵɵreadContextLet,
'ɵɵsanitizeHtml': sanitization.ɵɵsanitizeHtml,
'ɵɵsanitizeStyle': sanitization.ɵɵsanitizeStyle,

View file

@ -1628,6 +1628,9 @@
{
"name": "init_lang"
},
{
"name": "init_let_declaration"
},
{
"name": "init_lift"
},