diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index 6df627e305..b9b96fcff5 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -3128,6 +3128,159 @@ describe('compiler compliance', () => { expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); }); + it('should not share pure functions between null and object literals', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + template: \` +
+ + \` + }) + export class MyApp {} + + @NgModule({declarations: [MyApp]}) + export class MyModule {} + ` + } + }; + + const MyAppDeclaration = ` + const $c0$ = function () { return { foo: null }; }; + const $c1$ = function () { return {}; }; + const $c2$ = function (a0) { return { foo: a0 }; }; + … + MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ + type: MyApp, + selectors: [["ng-component"]], + decls: 2, + vars: 6, + consts: [[${AttributeMarker.Bindings}, "dir"]], + template: function MyApp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelement(0, "div", 0); + $r3$.ɵɵelement(1, "div", 0); + } + if (rf & 2) { + $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$)); + $r3$.ɵɵadvance(1); + $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(4, $c2$, $r3$.ɵɵpureFunction0(3, $c1$))); + } + }, + encapsulation: 2 + }); + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); + }); + + it('should not share pure functions between null and array literals', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + template: \` + + + \` + }) + export class MyApp {} + + @NgModule({declarations: [MyApp]}) + export class MyModule {} + ` + } + }; + + const MyAppDeclaration = ` + const $c0$ = function () { return { foo: null }; }; + const $c1$ = function () { return []; }; + const $c2$ = function (a0) { return { foo: a0 }; }; + … + MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ + type: MyApp, + selectors: [["ng-component"]], + decls: 2, + vars: 6, + consts: [[${AttributeMarker.Bindings}, "dir"]], + template: function MyApp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelement(0, "div", 0); + $r3$.ɵɵelement(1, "div", 0); + } + if (rf & 2) { + $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$)); + $r3$.ɵɵadvance(1); + $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(4, $c2$, $r3$.ɵɵpureFunction0(3, $c1$))); + } + }, + encapsulation: 2 + }); + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); + }); + + it('should not share pure functions between null and function calls', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + template: \` + + + \` + }) + export class MyApp { + getFoo() { + return 'foo!'; + } + } + + @NgModule({declarations: [MyApp]}) + export class MyModule {} + ` + } + }; + + const MyAppDeclaration = ` + const $c0$ = function () { return { foo: null }; }; + const $c1$ = function (a0) { return { foo: a0 }; }; + … + MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ + type: MyApp, + selectors: [["ng-component"]], + decls: 2, + vars: 5, + consts: [[${AttributeMarker.Bindings}, "dir"]], + template: function MyApp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelement(0, "div", 0); + $r3$.ɵɵelement(1, "div", 0); + } + if (rf & 2) { + $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$)); + $r3$.ɵɵadvance(1); + $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(3, $c1$, ctx.getFoo())); + } + }, + encapsulation: 2 + }); + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); + }); + }); describe('inherited base classes', () => { diff --git a/packages/compiler/src/constant_pool.ts b/packages/compiler/src/constant_pool.ts index 129b39ac01..7be8ff12a2 100644 --- a/packages/compiler/src/constant_pool.ts +++ b/packages/compiler/src/constant_pool.ts @@ -11,6 +11,16 @@ import {OutputContext, error} from './util'; const CONSTANT_PREFIX = '_c'; +/** + * `ConstantPool` tries to reuse literal factories when two or more literals are identical. + * We determine whether literals are identical by creating a key out of their AST using the + * `KeyVisitor`. This constant is used to replace dynamic expressions which can't be safely + * converted into a key. E.g. given an expression `{foo: bar()}`, since we don't know what + * the result of `bar` will be, we create a key that looks like `{foo: