8000 fix(compiler): incorrectly handling let declarations inside i18n (#60… · angular/angular@e0d378d · GitHub
[go: up one dir, main page]

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
@@ - 67E6 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 numberD 10000 iff 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)
0