fix(ivy): prevent invalid forward references in setClassMetadata call (#27561)
In Ivy, a pure call to `setClassMetadata` is inserted to retain the information that would otherwise be lost while eliding the Angular decorators. In the past, the Angular constructor decorators were wrapped inside of an anonymous function which was only evaluated once `ReflectionCapabilities` was requested for such metadata. This approach prevents forward references from inside the constructor parameter decorators from being evaluated before they are available. In the `setClassMetadata` call, the constructor parameters were not wrapped within an anonymous function, such that forward references were evaluated too early, causing runtime errors. This commit changes the `setClassMetadata` call to pass the constructor parameter decorators inside of an anonymous function again, such that forward references are not resolved until requested by `ReflectionCapabilities`, therefore avoiding the early reads of forward refs. PR Close #27561
This commit is contained in:
parent
a8ebc837ea
commit
a9543457ef
|
@ -45,8 +45,15 @@ export function generateSetClassMetadataCall(
|
||||||
let metaCtorParameters: ts.Expression = ts.createNull();
|
let metaCtorParameters: ts.Expression = ts.createNull();
|
||||||
const classCtorParameters = reflection.getConstructorParameters(clazz);
|
const classCtorParameters = reflection.getConstructorParameters(clazz);
|
||||||
if (classCtorParameters !== null) {
|
if (classCtorParameters !== null) {
|
||||||
metaCtorParameters = ts.createArrayLiteral(
|
const ctorParameters = ts.createArrayLiteral(
|
||||||
classCtorParameters.map(param => ctorParameterToMetadata(param, isCore)));
|
classCtorParameters.map(param => ctorParameterToMetadata(param, isCore)));
|
||||||
|
metaCtorParameters = ts.createFunctionExpression(
|
||||||
|
/* modifiers */ undefined,
|
||||||
|
/* asteriskToken */ undefined,
|
||||||
|
/* name */ undefined,
|
||||||
|
/* typeParameters */ undefined,
|
||||||
|
/* parameters */ undefined,
|
||||||
|
/* type */ undefined, ts.createBlock([ts.createReturn(ctorParameters)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the same for property decorators.
|
// Do the same for property decorators.
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe('ngtsc setClassMetadata converter', () => {
|
||||||
`/*@__PURE__*/ i0.ɵsetClassMetadata(Target, [{ type: Component, args: ['metadata'] }], null, null);`);
|
`/*@__PURE__*/ i0.ɵsetClassMetadata(Target, [{ type: Component, args: ['metadata'] }], null, null);`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should convert decorated class construtor parameter metadata', () => {
|
it('should convert decorated class constructor parameter metadata', () => {
|
||||||
const res = compileAndPrint(`
|
const res = compileAndPrint(`
|
||||||
import {Component, Inject, Injector} from '@angular/core';
|
import {Component, Inject, Injector} from '@angular/core';
|
||||||
const FOO = 'foo';
|
const FOO = 'foo';
|
||||||
|
@ -45,7 +45,7 @@ describe('ngtsc setClassMetadata converter', () => {
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
expect(res).toContain(
|
expect(res).toContain(
|
||||||
`[{ type: undefined, decorators: [{ type: Inject, args: [FOO] }] }, { type: Injector }], null);`);
|
`function () { return [{ type: undefined, decorators: [{ type: Inject, args: [FOO] }] }, { type: Injector }]; }, null);`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should convert decorated field metadata', () => {
|
it('should convert decorated field metadata', () => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {Type} from '../type';
|
||||||
|
|
||||||
interface TypeWithMetadata extends Type<any> {
|
interface TypeWithMetadata extends Type<any> {
|
||||||
decorators?: any[];
|
decorators?: any[];
|
||||||
ctorParameters?: any[];
|
ctorParameters?: () => any[];
|
||||||
propDecorators?: {[field: string]: any};
|
propDecorators?: {[field: string]: any};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ interface TypeWithMetadata extends Type<any> {
|
||||||
* tree-shaken away during production builds.
|
* tree-shaken away during production builds.
|
||||||
*/
|
*/
|
||||||
export function setClassMetadata(
|
export function setClassMetadata(
|
||||||
type: Type<any>, decorators: any[] | null, ctorParameters: any[] | null,
|
type: Type<any>, decorators: any[] | null, ctorParameters: (() => any[]) | null,
|
||||||
propDecorators: {[field: string]: any} | null): void {
|
propDecorators: {[field: string]: any} | null): void {
|
||||||
const clazz = type as TypeWithMetadata;
|
const clazz = type as TypeWithMetadata;
|
||||||
if (decorators !== null) {
|
if (decorators !== null) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ interface Decorator {
|
||||||
|
|
||||||
interface HasMetadata extends Type<any> {
|
interface HasMetadata extends Type<any> {
|
||||||
decorators?: Decorator[];
|
decorators?: Decorator[];
|
||||||
ctorParameters: {type: any, decorators?: Decorator[]}[];
|
ctorParameters: () => CtorParameter[];
|
||||||
propDecorators: {[field: string]: Decorator[]};
|
propDecorators: {[field: string]: Decorator[]};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,9 +45,9 @@ describe('render3 setClassMetadata()', () => {
|
||||||
|
|
||||||
it('should set ctor parameter metadata on a type', () => {
|
it('should set ctor parameter metadata on a type', () => {
|
||||||
const Foo = metadataOf(class Foo{});
|
const Foo = metadataOf(class Foo{});
|
||||||
Foo.ctorParameters = [{type: 'initial'}];
|
Foo.ctorParameters = () => [{type: 'initial'}];
|
||||||
setClassMetadata(Foo, null, [{type: 'test'}], null);
|
setClassMetadata(Foo, null, () => [{type: 'test'}], null);
|
||||||
expect(Foo.ctorParameters).toEqual([{type: 'test'}]);
|
expect(Foo.ctorParameters()).toEqual([{type: 'test'}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set parameter decorator metadata on a type', () => {
|
it('should set parameter decorator metadata on a type', () => {
|
||||||
|
|
Loading…
Reference in New Issue