Skip to content

Commit e0d378d

Browse files
crisbetoalxhub
authored andcommitted
fix(compiler): incorrectly handling let declarations inside i18n (#60512)
The compiler wasn't handling `@let` declarations placed inside i18n blocks. The problem is that `assignI18nSlotDependencies` phase assigns the `target` of i18n ops much earlier than the `@let` optimization. If the `@let` ends up getting optimized because it isn't used in any child views, the pointer in the i18n instruction becomes invalid. This hadn't surfaced so far, because we didn't optimize `declareLet` ops, however once we do, we start hitting assertions that the optimized `declareLet` isn't used anywhere. These changes resolve the issue by moving the i18n phases after the `@let` optimization. PR Close #60512
1 parent 768239a commit e0d378d

12 files changed

+506
-5
lines changed

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/GOLDEN_PARTIAL.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,3 +792,159 @@ export declare class MyApp {
792792
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
793793
}
794794

795+
/****************************************************************************************************
796+
* PARTIAL FILE: let_in_i18n.js
797+
****************************************************************************************************/
798+
import { Component } from '@angular/core';
799+
import * as i0 from "@angular/core";
800+
export class MyApp {
801+
constructor() {
802+
this.value = 1;
803+
}
804+
}
805+
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
806+
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
807+
<div i18n>
808+
@let result = value * 2;
809+
The result is {{result}}
810+
</div>
811+
`, isInline: true });
812+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
813+
type: Component,
814+
args: [{
815+
template: `
816+
<div i18n>
817+
@let result = value * 2;
818+
The result is {{result}}
819+
</div>
820+
`,
821+
}]
822+
}] });
823+
824+
/****************************************************************************************************
825+
* PARTIAL FILE: let_in_i18n.d.ts
826+
****************************************************************************************************/
827+
import * as i0 from "@angular/core";
828+
export declare class MyApp {
829+
value: number;
830+
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
831+
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
832+
}
833+
834+
/****************************************************************************************************
835+
* PARTIAL FILE: let_in_child_view_inside_i18n.js
836+
****************************************************************************************************/
837+
import { Component } from '@angular/core';
838+
import * as i0 from "@angular/core";
839+
export class MyApp {
840+
constructor() {
841+
this.value = 1;
842+
}
843+
}
844+
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
845+
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
846+
<div i18n>
847+
@let result = value * 2;
848+
<ng-template>The result is {{result}}</ng-template>
849+
</div>
850+
`, isInline: true });
851+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
852+
type: Component,
853+
args: [{
854+
template: `
855+
<div i18n>
856+
@let result = value * 2;
857+
<ng-template>The result is {{result}}</ng-template>
858+
</div>
859+
`,
860+
}]
861+
}] });
862+
863+
/****************************************************************************************************
864+
* PARTIAL FILE: let_in_child_view_inside_i18n.d.ts
865+
****************************************************************************************************/
866+
import * as i0 from "@angular/core";
867+
export declare class MyApp {
868+
value: number;
869+
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
870+
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
871+
}
872+
873+
/****************************************************************************************************
874+
* PARTIAL FILE: let_in_i18n_and_child_view.js
875+
****************************************************************************************************/
876+
import { Component } from '@angular/core';
877+
import * as i0 from "@angular/core";
878+
export class MyApp {
879+
constructor() {
880+
this.value = 1;
881+
}
882+
}
883+
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
884+
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
885+
<div i18n>
886+
@let result = value * 2;
887+
The result is {{result}}
888+
<ng-template>To repeat, the result is {{result}}</ng-template>
889+
</div>
890+
`, isInline: true });
891+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
892+
type: Component,
893+
args: [{
894+
template: `
895+
<div i18n>
896+
@let result = value * 2;
897+
The result is {{result}}
898+
<ng-template>To repeat, the result is {{result}}</ng-template>
899+
</div>
900+
`,
901+
}]
902+
}] });
903+
904+
/****************************************************************************************************
905+
* PARTIAL FILE: let_in_i18n_and_child_view.d.ts
906+
****************************************************************************************************/
907+
import * as i0 from "@angular/core";
908+
export declare class MyApp {
909+
value: number;
910+
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
911+
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
912+
}
913+
914+
/****************************************************************************************************
915+
* PARTIAL FILE: let_preceded_by_i18n.js
916+
****************************************************************************************************/
917+
import { Component } from '@angular/core';
918+
import * as i0 from "@angular/core";
919+
export class MyApp {
920+
constructor() {
921+
this.value = 1;
922+
}
923+
}
924+
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
925+
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
926+
<div i18n>Hello {{value}}</div>
927+
@let result = value * 2;
928+
<ng-template>The result is {{result}}</ng-template>
929+
`, isInline: true });
930+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
931+
type: Component,
932+
args: [{
933+
template: `
934+
<div i18n>Hello {{value}}</div>
935+
@let result = value * 2;
936+
<ng-template>The result is {{result}}</ng-template>
937+
`,
938+
}]
939+
}] });
940+
941+
/****************************************************************************************************
942+
* PARTIAL FILE: let_preceded_by_i18n.d.ts
943+
****************************************************************************************************/
944+
import * as i0 from "@angular/core";
945+
export declare class MyApp {
946+
value: number;
947+
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
948+
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
949+
}
950+

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/TEST_CASES.json

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,74 @@
309309
"failureMessage": "Incorrect template"
310310
}
311311
]
312+
},
313+
{
314+
"description": "should handle an @let used only directly inside i18n",
315+
"inputFiles": [
316+
"let_in_i18n.ts"
317+
],
318+
"expectations": [
319+
{
320+
"files": [
321+
{
322+
"expected": "let_in_i18n_template.js",
323+
"generated": "let_in_i18n.js"
324+
}
325+
],
326+
"failureMessage": "Incorrect template"
327+
}
328+
]
329+
},
330+
{
331+
"description": "should handle an @let referenced inside a child view inside i18n",
332+
"inputFiles": [
333+
"let_in_child_view_inside_i18n.ts"
334+
],
335+
"expectations": [
336+
{
337+
"files": [
338+
{
339+
"expected": "let_in_child_view_inside_i18n_template.js",
340+
"generated": "let_in_child_view_inside_i18n.js"
341+
}
342+
],
343+
"failureMessage": "Incorrect template"
344+
}
345+
]
346+
},
347+
{
348+
"description": "should handle an @let referenced inside i18n and in a child view",
349+
"inputFiles": [
350+
"let_in_i18n_and_child_view.ts"
351+
],
352+
"expectations": [
353+
{
354+
"files": [
355+
{
356+
"expected": "let_in_i18n_and_child_view_template.js",
357+
"generated": "let_in_i18n_and_child_view.js"
358+
}
359+
],
360+
"failureMessage": "Incorrect template"
361+
}
362+
]
363+
},
364+
{
365+
"description": "should handle an @let preceded by an element with i18n",
366+
"inputFiles": [
367+
"let_preceded_by_i18n.ts"
368+
],
369+
"expectations": [
370+
{
371+
"files": [
372+
{
373+
"expected": "let_preceded_by_i18n_template.js",
374+
"generated": "let_preceded_by_i18n.js"
375+
}
376+
],
377+
"failureMessage": "Incorrect template"
378+
}
379+
]
312380
}
313381
]
314382
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {Component} from '@angular/core';
2+
3+
@Component({
4+
template: `
5+
<div i18n>
6+
@let result = value * 2;
7+
<ng-template>The result is {{result}}</ng-template>
8+
</div>
9+
`,
10+
})
11+
export class MyApp {
12+
value = 1;
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
function MyApp_ng_template_3_Template(rf, ctx) {
2+
if (rf & 1) {
3+
$r3$.ɵɵi18n(0, 0, 1);
4+
}
5+
if (rf & 2) {
6+
$r3$.ɵɵnextContext();
7+
const $result_r1$ = $r3$.ɵɵreadContextLet(2);
8+
$r3$.ɵɵi18nExp($result_r1$);
9+
$r3$.ɵɵi18nApply(0);
10+
}
11+
}
12+
13+
14+
15+
$r3$.ɵɵdefineComponent({
16+
17+
decls: 4,
18+
vars: 1,
19+
consts: () => {
20+
let $i18n_0$;
21+
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
22+
/**
23+
* @suppress {msgDescriptions}
24+
*/
25+
const $MSG_ID_WITH_SUFFIX$ = goog.getMsg("{$startTagNgTemplate}The result is {$interpolation}{$closeTagNgTemplate}", {
26+
"closeTagNgTemplate": "\uFFFD/*3:1\uFFFD",
27+
"interpolation": "\uFFFD0:1\uFFFD",
28+
"startTagNgTemplate": "\uFFFD*3:1\uFFFD"
29+
}, {
30+
original_code: {
31+
"closeTagNgTemplate": "</ng-template>",
32+
"interpolation": "{{result}}",
33+
"startTagNgTemplate": "<ng-template>"
34+
}
35+
});
36+
$i18n_0$ = $MSG_ID_WITH_SUFFIX$;
37+
} else {
38+
$i18n_0$ = $localize `${"\uFFFD*3:1\uFFFD"}:START_TAG_NG_TEMPLATE:The result is ${"\uFFFD0:1\uFFFD"}:INTERPOLATION:${"\uFFFD/*3:1\uFFFD"}:CLOSE_TAG_NG_TEMPLATE:`;
39+
}
40+
return [$i18n_0$];
41+
},
42+
template: function MyApp_Template(rf, ctx) {
43+
if (rf & 1) {
44+
$r3$.ɵɵelementStart(0, "div");
45+
$r3$.ɵɵi18nStart(1, 0);
46+
$r3$.ɵɵdeclareLet(2);
47+
$r3$.ɵɵtemplate(3, MyApp_ng_template_3_Template, 1, 1, "ng-template");
48+
$r3$.ɵɵi18nEnd();
49+
$r3$.ɵɵelementEnd();
50+
}
51+
if (rf & 2) {
52+
$r3$.ɵɵadvance(2);
53+
$r3$.ɵɵstoreLet(ctx.value * 2);
54+
}
55+
},
56+
57+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {Component} from '@angular/core';
2+
3+
@Component({
4+
template: `
5+
<div i18n>
6+
@let result = value * 2;
7+
The result is {{result}}
8+
</div>
9+
`,
10+
})
11+
export class MyApp {
12+
value = 1;
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Component} from '@angular/core';
2+
3+
@Component({
4+
template: `
5+
<div i18n>
6+
@let result = value * 2;
7+
The result is {{result}}
8+
<ng-template>To repeat, the result is {{result}}</ng-template>
9+
</div>
10+
`,
11+
})
12+
export class MyApp {
13+
value = 1;
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
function MyApp_ng_template_3_Template(rf, ctx) {
2+
if (rf & 1) {
3+
$r3$.ɵɵi18n(0, 0, 1);
4+
}
5+
if (rf & 2) {
6+
$r3$.ɵɵnextContext();
7+
const $result_r1$ = $r3$.ɵɵreadContextLet(2);
8+
$r3$.ɵɵi18nExp($result_r1$);
9+
$r3$.ɵɵi18nApply(0);
10+
}
11+
}
12+
13+
14+
15+
$r3$.ɵɵdefineComponent({
16+
17+
decls: 4,
18+
vars: 2,
19+
consts: () => {
20+
let $i18n_0$;
21+
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
22+
/**
23+
* @suppress {msgDescriptions}
24+
*/
25+
const $MSG_ID_WITH_SUFFIX$ = goog.getMsg(" The result is {$interpolation} {$startTagNgTemplate}To repeat, the result is {$interpolation}{$closeTagNgTemplate}", {
26+
"closeTagNgTemplate": "\uFFFD/*3:1\uFFFD",
27+
"interpolation": "[\uFFFD0\uFFFD|\uFFFD0:1\uFFFD]",
28+
"startTagNgTemplate": "\uFFFD*3:1\uFFFD"
29+
}, {
30+
original_code: {
31+
"closeTagNgTemplate": "</ng-template>",
32+
"interpolation": "{{result}}",
33+
"startTagNgTemplate": "<ng-template>"
34+
}
35+
});
36+
$i18n_0$ = $MSG_ID_WITH_SUFFIX$;
37+
} else {
38+
$i18n_0$ = $localize ` The result is ${"[\uFFFD0\uFFFD|\uFFFD0:1\uFFFD]"}:INTERPOLATION: ${"\uFFFD*3:1\uFFFD"}:START_TAG_NG_TEMPLATE:To repeat, the result is ${"[\uFFFD0\uFFFD|\uFFFD0:1\uFFFD]"}:INTERPOLATION:${"\uFFFD/*3:1\uFFFD"}:CLOSE_TAG_NG_TEMPLATE:`;
39+
}
40+
$i18n_0$ = $r3$.ɵɵi18nPostprocess($i18n_0$);
41+
return [$i18n_0$];
42+
},
43+
template: function MyApp_Template(rf, ctx) {
44+
if (rf & 1) {
45+
$r3$.ɵɵelementStart(0, "div");
46+
$r3$.ɵɵi18nStart(1, 0);
47+
$r3$.ɵɵdeclareLet(2);
48+
$r3$.ɵɵtemplate(3, MyApp_ng_template_3_Template, 1, 1, "ng-template");
49+
$r3$.ɵɵi18nEnd();
50+
$r3$.ɵɵelementEnd();
51+
}
52+
if (rf & 2) {
53+
$r3$.ɵɵadvance(2);
54+
const $result_r2$ = $r3$.ɵɵstoreLet(ctx.value * 2);
55+
$r3$.ɵɵadvance();
56+
$r3$.ɵɵi18nExp($result_r2$);
57+
$r3$.ɵɵi18nApply(1);
58+
}
59+
},
60+
61+
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy