From bc655bf309dcc313daa647cc0a28f52c95a769b2 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sat, 8 Jun 2024 09:52:15 +0200 Subject: [PATCH] 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 --- .../partial_component_linker_1.ts | 2 + .../r3_view_compiler_let/GOLDEN_PARTIAL.js | 717 ++++++++++++++++++ .../r3_view_compiler_let/TEST_CASES.json | 326 ++++++++ .../r3_view_compiler_let/let_for_loop.ts | 19 + .../let_for_loop_template.js | 47 ++ .../r3_view_compiler_let/let_in_child_view.ts | 17 + .../let_in_child_view_listener.ts | 30 + .../let_in_child_view_listener_template.js | 79 ++ .../let_in_child_view_template.js | 49 ++ .../r3_view_compiler_let/let_in_listener.ts | 18 + .../let_in_listener_template.js | 26 + .../let_invalid_forward_ref.ts | 14 + .../let_invalid_forward_ref_template.js | 19 + .../let_local_forward_refs.ts | 11 + .../let_local_forward_refs_template.js | 20 + .../r3_view_compiler_let/let_local_refs.ts | 13 + .../let_local_refs_template.js | 21 + .../let_multiple_optimization.ts | 16 + .../let_multiple_optimization_template.js | 22 + .../let_optimization_child_view.ts | 19 + .../let_optimization_child_view_template.js | 39 + .../let_optimization_listener.ts | 21 + .../let_optimization_listener_template.js | 32 + .../let_partial_optimization.ts | 16 + .../let_partial_optimization_template.js | 22 + .../let_shared_with_child_view.ts | 11 + .../let_shared_with_child_view_template.js | 31 + .../let_single_optimization.ts | 13 + .../let_single_optimization_template.js | 19 + .../r3_view_compiler_let/let_with_pipe.ts | 24 + .../let_with_pipe_template.js | 19 + .../r3_view_compiler_let/multiple_let.ts | 14 + .../multiple_let_template.js | 19 + .../r3_view_compiler_let/simple_let.ts | 12 + .../simple_let_template.js | 17 + .../compiler/src/render3/r3_identifiers.ts | 4 + .../src/template/pipeline/ir/src/enums.ts | 20 + .../template/pipeline/ir/src/expression.ts | 83 +- .../template/pipeline/ir/src/ops/create.ts | 32 +- .../template/pipeline/ir/src/ops/update.ts | 41 +- .../src/template/pipeline/ir/src/traits.ts | 9 +- .../src/template/pipeline/src/emit.ts | 6 + .../src/template/pipeline/src/ingest.ts | 16 +- .../src/template/pipeline/src/instruction.ts | 12 + .../template/pipeline/src/phases/chaining.ts | 1 + .../pipeline/src/phases/generate_advance.ts | 34 +- .../phases/generate_local_let_references.ts | 41 + .../pipeline/src/phases/generate_variables.ts | 54 +- .../src/phases/next_context_merging.ts | 1 + .../src/template/pipeline/src/phases/reify.ts | 9 + .../phases/remove_illegal_let_references.ts | 46 ++ .../pipeline/src/phases/resolve_names.ts | 2 +- .../pipeline/src/phases/save_restore_view.ts | 2 +- .../src/phases/store_let_optimization.ts | 45 ++ .../pipeline/src/phases/var_counting.ts | 3 + .../src/phases/variable_optimization.ts | 3 + .../core/src/core_render3_private_export.ts | 3 + packages/core/src/render3/index.ts | 3 + packages/core/src/render3/instructions/all.ts | 1 + .../render3/instructions/let_declaration.ts | 41 + packages/core/src/render3/jit/environment.ts | 3 + .../bundling/defer/bundle.golden_symbols.json | 3 + 62 files changed, 2286 insertions(+), 26 deletions(-) create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/GOLDEN_PARTIAL.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/TEST_CASES.json create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_for_loop.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_for_loop_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_listener.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_listener_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_listener.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_listener_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_invalid_forward_ref.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_invalid_forward_ref_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_forward_refs.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_forward_refs_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_refs.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_refs_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_multiple_optimization.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_multiple_optimization_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_child_view.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_child_view_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_listener.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_listener_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_partial_optimization.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_partial_optimization_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_shared_with_child_view.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_shared_with_child_view_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_single_optimization.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_single_optimization_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_with_pipe.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_with_pipe_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/multiple_let.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/multiple_let_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/simple_let.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/simple_let_template.js create mode 100644 packages/compiler/src/template/pipeline/src/phases/generate_local_let_references.ts create mode 100644 packages/compiler/src/template/pipeline/src/phases/remove_illegal_let_references.ts create mode 100644 packages/compiler/src/template/pipeline/src/phases/store_let_optimization.ts create mode 100644 packages/core/src/render3/instructions/let_declaration.ts diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts index 0e50779a3b8..69fccbd3502 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts @@ -104,6 +104,7 @@ export class PartialComponentLinkerVersion1 // 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 // 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'); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/GOLDEN_PARTIAL.js new file mode 100644 index 00000000000..77073b2f7b5 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/GOLDEN_PARTIAL.js @@ -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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + static ɵpipe: i0.ɵɵPipeDeclaration; +} +export declare class MyApp { + value: number; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + + + `, 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; + + + `, + 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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}} + {{value}} + `, 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}} + {{value}} + `, + 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + + + @let two = one + 1; + + @if (true) { + @let three = two + 1; + + @switch (1) { + @case (1) { + @let four = three + 1; + + } + } + } + + `, 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; + + @if (true) { + @let three = two + 1; + + @switch (1) { + @case (1) { + @let four = three + 1; + + } + } + } + + `, + 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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: ` + + + + @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: ` + + + + @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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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}} + + `, 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}} + + `, + standalone: true + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: let_local_forward_refs.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyApp { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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: ` + + {{result}} + @let result = value * 2; + + `, isInline: true }); +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{ + type: Component, + args: [{ + template: ` + + {{result}} + @let result = value * 2; + + `, + 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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}} + + `, 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_optimization_listener.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyApp { + value: number; + callback(value: number): void; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * 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; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/TEST_CASES.json new file mode 100644 index 00000000000..008fdbf9c49 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/TEST_CASES.json @@ -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" + } + ] + } + ] +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_for_loop.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_for_loop.ts new file mode 100644 index 00000000000..4ad25429e24 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_for_loop.ts @@ -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[]}[] = []; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_for_loop_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_for_loop_template.js new file mode 100644 index 00000000000..bea004f803b --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_for_loop_template.js @@ -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); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view.ts new file mode 100644 index 00000000000..58831302eea --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view.ts @@ -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 {} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_listener.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_listener.ts new file mode 100644 index 00000000000..d944d8bca96 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_listener.ts @@ -0,0 +1,30 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + @let one = value + 1; + + + @let two = one + 1; + + @if (true) { + @let three = two + 1; + + @switch (1) { + @case (1) { + @let four = three + 1; + + } + } + } + + `, + standalone: true, +}) +export class MyApp { + value = 1; + + callback(one: number, two: number, three: number, four: number) { + console.log(one, two, three, four); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_listener_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_listener_template.js new file mode 100644 index 00000000000..8f6862a8de9 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_listener_template.js @@ -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); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_template.js new file mode 100644 index 00000000000..193c9c46cba --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_template.js @@ -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); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_listener.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_listener.ts new file mode 100644 index 00000000000..e478fe6724e --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_listener.ts @@ -0,0 +1,18 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + @let one = value + 1; + @let two = one + 1; + + + `, + standalone: true, +}) +export class MyApp { + value = 1; + + callback(one: number, two: number) { + console.log(one, two); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_listener_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_listener_template.js new file mode 100644 index 00000000000..c428f2de42d --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_listener_template.js @@ -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); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_invalid_forward_ref.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_invalid_forward_ref.ts new file mode 100644 index 00000000000..4a218dc60c2 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_invalid_forward_ref.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + + {{result}} + @let result = value * 2; + + `, + standalone: true, +}) +export class MyApp { + value = 1; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_invalid_forward_ref_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_invalid_forward_ref_template.js new file mode 100644 index 00000000000..70018726307 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_invalid_forward_ref_template.js @@ -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"); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_forward_refs.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_forward_refs.ts new file mode 100644 index 00000000000..98dea7b99a5 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_forward_refs.ts @@ -0,0 +1,11 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + @let message = 'Hello, ' + name.value; + {{message}} + + `, + standalone: true, +}) +export class MyApp {} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_forward_refs_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_forward_refs_template.js new file mode 100644 index 00000000000..2357d55fa19 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_forward_refs_template.js @@ -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$, " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_refs.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_refs.ts new file mode 100644 index 00000000000..382f7e51d46 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_refs.ts @@ -0,0 +1,13 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + + + + @let fullName = name.value + ' ' + lastName.value; + Hello, {{fullName}} + `, + standalone: true, +}) +export class MyApp {} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_refs_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_refs_template.js new file mode 100644 index 00000000000..adc8cbaaa8f --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_local_refs_template.js @@ -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$, " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_multiple_optimization.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_multiple_optimization.ts new file mode 100644 index 00000000000..28a18881290 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_multiple_optimization.ts @@ -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; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_multiple_optimization_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_multiple_optimization_template.js new file mode 100644 index 00000000000..77f8859f7fc --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_multiple_optimization_template.js @@ -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, " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_child_view.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_child_view.ts new file mode 100644 index 00000000000..9532f493cc5 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_child_view.ts @@ -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; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_child_view_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_child_view_template.js new file mode 100644 index 00000000000..e1957f32f50 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_child_view_template.js @@ -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); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_listener.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_listener.ts new file mode 100644 index 00000000000..cfacf4c72a1 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_listener.ts @@ -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}} + + `, + standalone: true, +}) +export class MyApp { + value = 0; + + callback(value: number) { + console.log(value); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_listener_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_listener_template.js new file mode 100644 index 00000000000..987c10c04c3 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_optimization_listener_template.js @@ -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, " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_partial_optimization.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_partial_optimization.ts new file mode 100644 index 00000000000..44884155a0d --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_partial_optimization.ts @@ -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; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_partial_optimization_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_partial_optimization_template.js new file mode 100644 index 00000000000..a0433ca2008 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_partial_optimization_template.js @@ -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$, " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_shared_with_child_view.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_shared_with_child_view.ts new file mode 100644 index 00000000000..fc4b7b92652 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_shared_with_child_view.ts @@ -0,0 +1,11 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + @let value = 123; + {{value}} + {{value}} + `, + standalone: true, +}) +export class MyApp {} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_shared_with_child_view_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_shared_with_child_view_template.js new file mode 100644 index 00000000000..768b84ef1f6 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_shared_with_child_view_template.js @@ -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$, " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_single_optimization.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_single_optimization.ts new file mode 100644 index 00000000000..7243f76172a --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_single_optimization.ts @@ -0,0 +1,13 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + {{value}} + @let result = value * 2; + {{value}} + `, + standalone: true, +}) +export class MyApp { + value = 0; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_single_optimization_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_single_optimization_template.js new file mode 100644 index 00000000000..bb1c2d7d1c1 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_single_optimization_template.js @@ -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, " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_with_pipe.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_with_pipe.ts new file mode 100644 index 00000000000..1958b782feb --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_with_pipe.ts @@ -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; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_with_pipe_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_with_pipe_template.js new file mode 100644 index 00000000000..eee169658cf --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_with_pipe_template.js @@ -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$, " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/multiple_let.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/multiple_let.ts new file mode 100644 index 00000000000..30bbf4211b7 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/multiple_let.ts @@ -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; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/multiple_let_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/multiple_let_template.js new file mode 100644 index 00000000000..4aa7e827b73 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/multiple_let_template.js @@ -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$, " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/simple_let.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/simple_let.ts new file mode 100644 index 00000000000..b2689c95433 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/simple_let.ts @@ -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; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/simple_let_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/simple_let_template.js new file mode 100644 index 00000000000..8a33a74e60f --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/simple_let_template.js @@ -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$, " "); + } + }, + … +}); diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index be62276d921..319a4c6677c 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -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 = { diff --git a/packages/compiler/src/template/pipeline/ir/src/enums.ts b/packages/compiler/src/template/pipeline/ir/src/enums.ts index 0c5d0ea705f..9c3aeee85d7 100644 --- a/packages/compiler/src/template/pipeline/ir/src/enums.ts +++ b/packages/compiler/src/template/pipeline/ir/src/enums.ts @@ -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. */ diff --git a/packages/compiler/src/template/pipeline/ir/src/expression.ts b/packages/compiler/src/template/pipeline/ir/src/expression.ts index ef3a6a50889..dab14d5fee2 100644 --- a/packages/compiler/src/template/pipeline/ir/src/expression.ts +++ b/packages/compiler/src/template/pipeline/ir/src/expression.ts @@ -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: diff --git a/packages/compiler/src/template/pipeline/ir/src/ops/create.ts b/packages/compiler/src/template/pipeline/ir/src/ops/create.ts index bf3dfae7b8f..d2231eee3e0 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/create.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/create.ts @@ -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, 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. diff --git a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts index e0884638025..07d1a65daa8 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts @@ -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, 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, + }; +} diff --git a/packages/compiler/src/template/pipeline/ir/src/traits.ts b/packages/compiler/src/template/pipeline/ir/src/traits.ts index 99ed999ab20..bab17e90d28 100644 --- a/packages/compiler/src/template/pipeline/ir/src/traits.ts +++ b/packages/compiler/src/template/pipeline/ir/src/traits.ts @@ -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>( /** * Test whether an operation implements `DependsOnSlotContextOpTrait`. */ +export function hasDependsOnSlotContextTrait( + expr: ExprT, +): expr is ExprT & DependsOnSlotContextOpTrait; export function hasDependsOnSlotContextTrait>( op: OpT, -): op is OpT & DependsOnSlotContextOpTrait { - return (op as Partial)[DependsOnSlotContext] === true; +): op is OpT & DependsOnSlotContextOpTrait; +export function hasDependsOnSlotContextTrait(value: any): boolean { + return (value as Partial)[DependsOnSlotContext] === true; } /** diff --git a/packages/compiler/src/template/pipeline/src/emit.ts b/packages/compiler/src/template/pipeline/src/emit.ts index b3ce5d87c99..4b277d0e109 100644 --- a/packages/compiler/src/template/pipeline/src/emit.ts +++ b/packages/compiler/src/template/pipeline/src/emit.ts @@ -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}, diff --git a/packages/compiler/src/template/pipeline/src/ingest.ts b/packages/compiler/src/template/pipeline/src/ingest.ts index bf2b0d94361..fe3ef0b44fb 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -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. */ diff --git a/packages/compiler/src/template/pipeline/src/instruction.ts b/packages/compiler/src/template/pipeline/src/instruction.ts index 688c5f80740..7f595717c15 100644 --- a/packages/compiler/src/template/pipeline/src/instruction.ts +++ b/packages/compiler/src/template/pipeline/src/instruction.ts @@ -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, diff --git a/packages/compiler/src/template/pipeline/src/phases/chaining.ts b/packages/compiler/src/template/pipeline/src/phases/chaining.ts index fe7dbdaa0af..662064ed2e3 100644 --- a/packages/compiler/src/template/pipeline/src/phases/chaining.ts +++ b/packages/compiler/src/template/pipeline/src/phases/chaining.ts @@ -40,6 +40,7 @@ const CHAINABLE = new Set([ R3.templateCreate, R3.twoWayProperty, R3.twoWayListener, + R3.declareLet, ]); /** diff --git a/packages/compiler/src/template/pipeline/src/phases/generate_advance.ts b/packages/compiler/src/template/pipeline/src/phases/generate_advance.ts index bc5b2f92061..94439124564 100644 --- a/packages/compiler/src/template/pipeline/src/phases/generate_advance.ts +++ b/packages/compiler/src/template/pipeline/src/phases/generate_advance.ts @@ -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.createAdvanceOp(delta, (op as ir.DependsOnSlotContextOpTrait).sourceSpan), - op, - ); + ir.OpList.insertBefore(ir.createAdvanceOp(delta, consumer.sourceSpan), op); slotContext = slot; } } diff --git a/packages/compiler/src/template/pipeline/src/phases/generate_local_let_references.ts b/packages/compiler/src/template/pipeline/src/phases/generate_local_let_references.ts new file mode 100644 index 00000000000..077dc932f87 --- /dev/null +++ b/packages/compiler/src/template/pipeline/src/phases/generate_local_let_references.ts @@ -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( + op, + ir.createVariableOp( + job.allocateXrefId(), + variable, + new ir.StoreLetExpr(op.target, op.value, op.sourceSpan), + ir.VariableFlags.None, + ), + ); + } + } +} diff --git a/packages/compiler/src/template/pipeline/src/phases/generate_variables.ts b/packages/compiler/src/template/pipeline/src/phases/generate_variables.ts index 820e726b486..5322392a2db 100644 --- a/packages/compiler/src/template/pipeline/src/phases/generate_variables.ts +++ b/packages/compiler/src/template/pipeline/src/phases/generate_variables.ts @@ -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(), 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[] { const newOps: ir.VariableOp[] = []; @@ -247,9 +278,22 @@ function generateVariablesInScopeForView( ); } + if (scope.view !== view.xref || isListener) { + for (const decl of scope.letDeclarations) { + newOps.push( + ir.createVariableOp( + 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; } diff --git a/packages/compiler/src/template/pipeline/src/phases/next_context_merging.ts b/packages/compiler/src/template/pipeline/src/phases/next_context_merging.ts index e7293f2f0a8..849d5ff0a11 100644 --- a/packages/compiler/src/template/pipeline/src/phases/next_context_merging.ts +++ b/packages/compiler/src/template/pipeline/src/phases/next_context_merging.ts @@ -78,6 +78,7 @@ function mergeNextContextsInOps(ops: ir.OpList): 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; diff --git a/packages/compiler/src/template/pipeline/src/phases/reify.ts b/packages/compiler/src/template/pipeline/src/phases/reify.ts index f6a29f23b78..81c67634c43 100644 --- a/packages/compiler/src/template/pipeline/src/phases/reify.ts +++ b/packages/compiler/src/template/pipeline/src/phases/reify.ts @@ -184,6 +184,9 @@ function reifyCreateOperations(unit: CompilationUnit, ops: ir.OpList + expr instanceof ir.LexicalReadExpr && expr.name === name ? o.literal(undefined) : expr, + ir.VisitorContextFlag.None, + ); + current = current.prev; + } + } + } +} diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_names.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_names.ts index c8e112e367e..8226d6fbdef 100644 --- a/packages/compiler/src/template/pipeline/src/phases/resolve_names.ts +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_names.ts @@ -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 diff --git a/packages/compiler/src/template/pipeline/src/phases/save_restore_view.ts b/packages/compiler/src/template/pipeline/src/phases/save_restore_view.ts index 248816db89a..972493de780 100644 --- a/packages/compiler/src/template/pipeline/src/phases/save_restore_view.ts +++ b/packages/compiler/src/template/pipeline/src/phases/save_restore_view.ts @@ -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; } diff --git a/packages/compiler/src/template/pipeline/src/phases/store_let_optimization.ts b/packages/compiler/src/template/pipeline/src/phases/store_let_optimization.ts new file mode 100644 index 00000000000..4467356a61a --- /dev/null +++ b/packages/compiler/src/template/pipeline/src/phases/store_let_optimization.ts @@ -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(); + + // 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, + ); + } + } +} diff --git a/packages/compiler/src/template/pipeline/src/phases/var_counting.ts b/packages/compiler/src/template/pipeline/src/phases/var_counting.ts index 3fe6bb7ef26..882caf0ec7f 100644 --- a/packages/compiler/src/template/pipeline/src/phases/var_counting.ts +++ b/packages/compiler/src/template/pipeline/src/phases/var_counting.ts @@ -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}`, diff --git a/packages/compiler/src/template/pipeline/src/phases/variable_optimization.ts b/packages/compiler/src/template/pipeline/src/phases/variable_optimization.ts index bf0b2c22d3e..948437c3db4 100644 --- a/packages/compiler/src/template/pipeline/src/phases/variable_optimization.ts +++ b/packages/compiler/src/template/pipeline/src/phases/variable_optimization.ts @@ -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; diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index a12f18bc1af..41de9c3be60 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -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'; diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 1f5eef60359..8118040a7d2 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -169,6 +169,9 @@ export { ɵsetUnknownElementStrictMode, ɵgetUnknownPropertyStrictMode, ɵsetUnknownPropertyStrictMode, + ɵɵdeclareLet, + ɵɵstoreLet, + ɵɵreadContextLet, } from './instructions/all'; export { DEFER_BLOCK_DEPENDENCY_INTERCEPTOR as ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR, diff --git a/packages/core/src/render3/instructions/all.ts b/packages/core/src/render3/instructions/all.ts index d911f3712cf..cdda69745b0 100644 --- a/packages/core/src/render3/instructions/all.ts +++ b/packages/core/src/render3/instructions/all.ts @@ -62,3 +62,4 @@ export * from './template'; export * from './text'; export * from './text_interpolation'; export * from './two_way'; +export * from './let_declaration'; diff --git a/packages/core/src/render3/instructions/let_declaration.ts b/packages/core/src/render3/instructions/let_declaration.ts new file mode 100644 index 00000000000..e6428e4470e --- /dev/null +++ b/packages/core/src/render3/instructions/let_declaration.ts @@ -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(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(index: number): T { + // TODO(crisbeto): implement this + return null as any; +} diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 6416025045d..82fdb865040 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -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, diff --git a/packages/core/test/bundling/defer/bundle.golden_symbols.json b/packages/core/test/bundling/defer/bundle.golden_symbols.json index 9a738059135..054a801674f 100644 --- a/packages/core/test/bundling/defer/bundle.golden_symbols.json +++ b/packages/core/test/bundling/defer/bundle.golden_symbols.json @@ -1628,6 +1628,9 @@ { "name": "init_lang" }, + { + "name": "init_let_declaration" + }, { "name": "init_lift" },