fix(ivy): incorrectly generating shared pure function between null and object literal (#35481)
In #33705 we made it so that we generate pure functions for object/array literals in order to avoid having them be shared across elements/views. The problem this introduced is that further down the line the `ContantPool` uses the generated literal in order to figure out whether to share an existing factory or to create a new one. `ConstantPool` determines whether to share a factory by creating a key from the AST node and using it to look it up in the factory cache, however the key generation function didn't handle function invocations and replaced them with `null`. This means that the key for `{foo: pureFunction0(...)}` and `{foo: null}` are the same. These changes rework the logic so that instead of generating a `null` key for function invocations, we generate a variable called `<unknown>` which shouldn't be able to collide with anything. Fixes #35298. PR Close #35481
This commit is contained in:
@ -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: \`
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: {}}"></div>
|
||||
\`
|
||||
})
|
||||
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: \`
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: []}"></div>
|
||||
\`
|
||||
})
|
||||
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: \`
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: getFoo()}"></div>
|
||||
\`
|
||||
})
|
||||
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', () => {
|
||||
|
Reference in New Issue
Block a user