fix(ivy): constant object literals shared across element and component instances (#33705)

Currently if a consumer does something like the following, the object literal will be shared across the two elements and any instances of the component template. The same applies to array literals:

```
<div [someDirective]="{}"></div>
<div [someDirective]="{}"></div>
```

These changes make it so that we generate a pure function even if an object is constant so that each instance gets its own object.

Note that the original design for this fix included moving the pure function factories into the `consts` array. In the process of doing so I realized that pure function are also used inside of directive host bindings which means that we don't have access to the `consts`.

These changes also:
* Fix an issue that meant that the `pureFunction0` instruction could only be run during creation mode.
* Make the `getConstant` utility slightly more convenient to use. This isn't strictly required for these changes to work, but I had made it as a part of a larger refactor that I ended up reverting.

PR Close #33705
This commit is contained in:
crisbeto
2019-11-13 22:26:34 +09:00
committed by Kara Erickson
parent 9a68f23dd2
commit fcdada53f1
9 changed files with 188 additions and 33 deletions

View File

@ -365,10 +365,10 @@ describe('compiler compliance', () => {
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div", 0);
$r3$.ɵɵpipe(1,"pipe");
$r3$.ɵɵpipe(1, "pipe");
}
if (rf & 2) {
$r3$.ɵɵproperty("ternary", ctx.cond ? $r3$.ɵɵpureFunction1(8, $c0$, ctx.a): $c1$)("pipe", $r3$.ɵɵpipeBind3(1, 4, ctx.value, 1, 2))("and", ctx.cond && $r3$.ɵɵpureFunction1(10, $c0$, ctx.b))("or", ctx.cond || $r3$.ɵɵpureFunction1(12, $c0$, ctx.c));
$r3$.ɵɵproperty("ternary", ctx.cond ? $r3$.ɵɵpureFunction1(8, $c0$, ctx.a): $r3$.ɵɵpureFunction0(10, $c1$))("pipe", $r3$.ɵɵpipeBind3(1, 4, ctx.value, 1, 2))("and", ctx.cond && $r3$.ɵɵpureFunction1(11, $c0$, ctx.b))("or", ctx.cond || $r3$.ɵɵpureFunction1(13, $c0$, ctx.c));
}
}
`;
@ -1082,25 +1082,24 @@ describe('compiler compliance', () => {
};
const MyAppDefinition = `
const $c0$ = {opacity: 0, duration: 0};
const $c0$ = function () { return {opacity: 0, duration: 0}; };
const $e0_ff$ = function ($v$) { return {opacity: 1, duration: $v$}; };
const $e0_ff_1$ = function ($v$) { return [$c0$, $v$]; };
const $e0_ff_1$ = function ($v1$, $v2$) { return [$v1$, $v2$]; };
const $e0_ff_2$ = function ($v1$, $v2$) { return {animation: $v1$, actions: $v2$}; };
MyApp.ɵcmp = $r3$.ɵɵdefineComponent({
type: MyApp,
selectors: [["my-app"]],
decls: 1,
vars: 8,
vars: 10,
consts: [[${AttributeMarker.Bindings}, "config"]],
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "nested-comp", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty(
"config",
$r3$.ɵɵpureFunction2(5, $e0_ff_2$, ctx.name, $r3$.ɵɵpureFunction1(3, $e0_ff_1$, $r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.duration))));
$r3$.ɵɵproperty("config",
$r3$.ɵɵpureFunction2(7, $e0_ff_2$, ctx.name, $r3$.ɵɵpureFunction2(4, $e0_ff_1$, $r3$.ɵɵpureFunction0(1, $c0$), $r3$.ɵɵpureFunction1(2, $e0_ff$, ctx.duration))));
}
},
directives: [NestedComp],
@ -2975,6 +2974,88 @@ describe('compiler compliance', () => {
expectEmit(result.source, expectedOutput, 'Invalid directive definition');
});
it('should generate a pure function for constant object literals', () => {
const files = {
app: {
'spec.ts': `
import {Component} from '@angular/core';
@Component({
template: '<some-comp [prop]="{}" [otherProp]="{a: 1, b: 2}"></some-comp>'
})
export class MyApp {
}
`
}
};
const MyAppDeclaration = `
const $c0$ = function () { return {}; };
const $c1$ = function () { return { a: 1, b: 2 }; };
MyApp.ɵcmp = $r3$.ɵɵdefineComponent({
type: MyApp,
selectors: [["ng-component"]],
decls: 1,
vars: 4,
consts: [[${AttributeMarker.Bindings}, "prop", "otherProp"]],
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "some-comp", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("prop", $r3$.ɵɵpureFunction0(2, $c0$))("otherProp", $r3$.ɵɵpureFunction0(3, $c1$));
}
},
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, MyAppDeclaration, 'Invalid component definition');
});
it('should generate a pure function for constant array literals', () => {
const files = {
app: {
'spec.ts': `
import {Component} from '@angular/core';
@Component({
template: '<some-comp [prop]="[]" [otherProp]="[0, 1, 2]"></some-comp>'
})
export class MyApp {
}
`
}
};
const MyAppDeclaration = `
const $c0$ = function () { return []; };
const $c1$ = function () { return [0, 1, 2]; };
MyApp.ɵcmp = $r3$.ɵɵdefineComponent({
type: MyApp,
selectors: [["ng-component"]],
decls: 1,
vars: 4,
consts: [[${AttributeMarker.Bindings}, "prop", "otherProp"]],
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "some-comp", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("prop", $r3$.ɵɵpureFunction0(2, $c0$))("otherProp", $r3$.ɵɵpureFunction0(3, $c1$));
}
},
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, MyAppDeclaration, 'Invalid component definition');
});
});
describe('inherited base classes', () => {