From a9543457ef0fd7b517ba58f787d1916cde7b9bb5 Mon Sep 17 00:00:00 2001 From: JoostK Date: Sun, 9 Dec 2018 15:31:55 +0100 Subject: [PATCH] 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 --- .../compiler-cli/src/ngtsc/annotations/src/metadata.ts | 9 ++++++++- .../src/ngtsc/annotations/test/metadata_spec.ts | 4 ++-- packages/core/src/render3/metadata.ts | 4 ++-- packages/core/test/render3/metadata_spec.ts | 8 ++++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/metadata.ts b/packages/compiler-cli/src/ngtsc/annotations/src/metadata.ts index 5f8c5d9bc9..c58b857603 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/metadata.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/metadata.ts @@ -45,8 +45,15 @@ export function generateSetClassMetadataCall( let metaCtorParameters: ts.Expression = ts.createNull(); const classCtorParameters = reflection.getConstructorParameters(clazz); if (classCtorParameters !== null) { - metaCtorParameters = ts.createArrayLiteral( + const ctorParameters = ts.createArrayLiteral( 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. diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts index 67ae1ac3a2..405e6ef6aa 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts @@ -35,7 +35,7 @@ describe('ngtsc setClassMetadata converter', () => { `/*@__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(` import {Component, Inject, Injector} from '@angular/core'; const FOO = 'foo'; @@ -45,7 +45,7 @@ describe('ngtsc setClassMetadata converter', () => { } `); 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', () => { diff --git a/packages/core/src/render3/metadata.ts b/packages/core/src/render3/metadata.ts index bb5b4be0ee..19988e1d2f 100644 --- a/packages/core/src/render3/metadata.ts +++ b/packages/core/src/render3/metadata.ts @@ -10,7 +10,7 @@ import {Type} from '../type'; interface TypeWithMetadata extends Type { decorators?: any[]; - ctorParameters?: any[]; + ctorParameters?: () => any[]; propDecorators?: {[field: string]: any}; } @@ -24,7 +24,7 @@ interface TypeWithMetadata extends Type { * tree-shaken away during production builds. */ export function setClassMetadata( - type: Type, decorators: any[] | null, ctorParameters: any[] | null, + type: Type, decorators: any[] | null, ctorParameters: (() => any[]) | null, propDecorators: {[field: string]: any} | null): void { const clazz = type as TypeWithMetadata; if (decorators !== null) { diff --git a/packages/core/test/render3/metadata_spec.ts b/packages/core/test/render3/metadata_spec.ts index 89d7f45ff4..26494a6e37 100644 --- a/packages/core/test/render3/metadata_spec.ts +++ b/packages/core/test/render3/metadata_spec.ts @@ -16,7 +16,7 @@ interface Decorator { interface HasMetadata extends Type { decorators?: Decorator[]; - ctorParameters: {type: any, decorators?: Decorator[]}[]; + ctorParameters: () => CtorParameter[]; propDecorators: {[field: string]: Decorator[]}; } @@ -45,9 +45,9 @@ describe('render3 setClassMetadata()', () => { it('should set ctor parameter metadata on a type', () => { const Foo = metadataOf(class Foo{}); - Foo.ctorParameters = [{type: 'initial'}]; - setClassMetadata(Foo, null, [{type: 'test'}], null); - expect(Foo.ctorParameters).toEqual([{type: 'test'}]); + Foo.ctorParameters = () => [{type: 'initial'}]; + setClassMetadata(Foo, null, () => [{type: 'test'}], null); + expect(Foo.ctorParameters()).toEqual([{type: 'test'}]); }); it('should set parameter decorator metadata on a type', () => {