From 159ab1c2570bb80c18b6e74fe23b69180af1e1b8 Mon Sep 17 00:00:00 2001 From: JoostK Date: Sat, 10 Nov 2018 02:58:33 +0100 Subject: [PATCH] fix(ivy): ngtsc should include generic types on injectable definitions (#27037) Analogously to directives, the `ngInjectableDef` field in .d.ts files is annotated with the type of service that it represents. If the service contains required generic type arguments, these must be included in the .d.ts file. PR Close #27037 --- .../src/ngtsc/annotations/src/injectable.ts | 9 +++++++-- packages/compiler-cli/test/ngtsc/ngtsc_spec.ts | 18 ++++++++++++++++++ .../compiler/src/compiler_facade_interface.ts | 1 + packages/compiler/src/injectable_compiler_2.ts | 11 ++++------- packages/compiler/src/jit_compiler_facade.ts | 1 + .../render3/jit/compiler_facade_interface.ts | 1 + packages/core/src/render3/jit/injectable.ts | 5 ++--- 7 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index 6dbfc1a5fd..d779bd4f41 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts @@ -74,6 +74,7 @@ function extractInjectableMetadata( const name = clazz.name.text; const type = new WrappedNodeExpr(clazz.name); const ctorDeps = getConstructorDependencies(clazz, reflector, isCore); + const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0; if (decorator.args === null) { throw new FatalDiagnosticError( ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called'); @@ -82,6 +83,7 @@ function extractInjectableMetadata( return { name, type, + typeArgumentCount, providedIn: new LiteralExpr(null), ctorDeps, }; } else if (decorator.args.length === 1) { @@ -118,6 +120,7 @@ function extractInjectableMetadata( return { name, type, + typeArgumentCount, ctorDeps, providedIn, useValue: new WrappedNodeExpr(meta.get('useValue') !) @@ -126,6 +129,7 @@ function extractInjectableMetadata( return { name, type, + typeArgumentCount, ctorDeps, providedIn, useExisting: new WrappedNodeExpr(meta.get('useExisting') !) @@ -134,6 +138,7 @@ function extractInjectableMetadata( return { name, type, + typeArgumentCount, ctorDeps, providedIn, useClass: new WrappedNodeExpr(meta.get('useClass') !), userDeps @@ -141,9 +146,9 @@ function extractInjectableMetadata( } else if (meta.has('useFactory')) { // useFactory is special - the 'deps' property must be analyzed. const factory = new WrappedNodeExpr(meta.get('useFactory') !); - return {name, type, providedIn, useFactory: factory, ctorDeps, userDeps}; + return {name, type, typeArgumentCount, providedIn, useFactory: factory, ctorDeps, userDeps}; } else { - return {name, type, providedIn, ctorDeps}; + return {name, type, typeArgumentCount, providedIn, ctorDeps}; } } else { throw new FatalDiagnosticError( diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index c3d79bb7fc..d9d944025c 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -46,6 +46,24 @@ describe('ngtsc behavioral tests', () => { expect(dtsContents).toContain('static ngInjectableDef: i0.ɵInjectableDef;'); }); + it('should compile Injectables with a generic service', () => { + env.tsconfig(); + env.write('test.ts', ` + import {Injectable} from '@angular/core'; + + @Injectable() + export class Store {} + `); + + env.driveMain(); + + + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain('Store.ngInjectableDef ='); + const dtsContents = env.getContents('test.d.ts'); + expect(dtsContents).toContain('static ngInjectableDef: i0.ɵInjectableDef>;'); + }); + it('should compile Components without errors', () => { env.tsconfig(); env.write('test.ts', ` diff --git a/packages/compiler/src/compiler_facade_interface.ts b/packages/compiler/src/compiler_facade_interface.ts index 5665359c73..f0da45188b 100644 --- a/packages/compiler/src/compiler_facade_interface.ts +++ b/packages/compiler/src/compiler_facade_interface.ts @@ -78,6 +78,7 @@ export interface R3PipeMetadataFacade { export interface R3InjectableMetadataFacade { name: string; type: any; + typeArgumentCount: number; ctorDeps: R3DependencyMetadataFacade[]|null; providedIn: any; useClass?: any; diff --git a/packages/compiler/src/injectable_compiler_2.ts b/packages/compiler/src/injectable_compiler_2.ts index e2b5845254..c2484b3ebc 100644 --- a/packages/compiler/src/injectable_compiler_2.ts +++ b/packages/compiler/src/injectable_compiler_2.ts @@ -10,7 +10,7 @@ import {InjectFlags} from './core'; import {Identifiers} from './identifiers'; import * as o from './output/output_ast'; import {R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata, compileFactoryFunction} from './render3/r3_factory'; -import {mapToMapExpression} from './render3/util'; +import {mapToMapExpression, typeWithParameters} from './render3/util'; export interface InjectableDef { expression: o.Expression; @@ -21,6 +21,7 @@ export interface InjectableDef { export interface R3InjectableMetadata { name: string; type: o.Expression; + typeArgumentCount: number; ctorDeps: R3DependencyMetadata[]|null; providedIn: o.Expression; useClass?: o.Expression; @@ -33,10 +34,6 @@ export interface R3InjectableMetadata { export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { let result: {factory: o.Expression, statements: o.Statement[]}|null = null; - function makeFn(ret: o.Expression): o.Expression { - return o.fn([], [new o.ReturnStatement(ret)], undefined, undefined, `${meta.name}_Factory`); - } - const factoryMeta = { name: meta.name, type: meta.type, @@ -100,8 +97,8 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { const expression = o.importExpr(Identifiers.defineInjectable).callFn([mapToMapExpression( {token, factory: result.factory, providedIn})]); - const type = new o.ExpressionType( - o.importExpr(Identifiers.InjectableDef, [new o.ExpressionType(meta.type)])); + const type = new o.ExpressionType(o.importExpr( + Identifiers.InjectableDef, [typeWithParameters(meta.type, meta.typeArgumentCount)])); return { expression, diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index e89c5d6691..85ffdd3613 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -45,6 +45,7 @@ export class CompilerFacadeImpl implements CompilerFacade { const {expression, statements} = compileInjectable({ name: facade.name, type: new WrappedNodeExpr(facade.type), + typeArgumentCount: facade.typeArgumentCount, providedIn: computeProvidedIn(facade.providedIn), useClass: wrapExpression(facade, USE_CLASS), useFactory: wrapExpression(facade, USE_FACTORY), diff --git a/packages/core/src/render3/jit/compiler_facade_interface.ts b/packages/core/src/render3/jit/compiler_facade_interface.ts index 5665359c73..f0da45188b 100644 --- a/packages/core/src/render3/jit/compiler_facade_interface.ts +++ b/packages/core/src/render3/jit/compiler_facade_interface.ts @@ -78,6 +78,7 @@ export interface R3PipeMetadataFacade { export interface R3InjectableMetadataFacade { name: string; type: any; + typeArgumentCount: number; ctorDeps: R3DependencyMetadataFacade[]|null; providedIn: any; useClass?: any; diff --git a/packages/core/src/render3/jit/injectable.ts b/packages/core/src/render3/jit/injectable.ts index 64a218d3a0..26f8b823e0 100644 --- a/packages/core/src/render3/jit/injectable.ts +++ b/packages/core/src/render3/jit/injectable.ts @@ -23,9 +23,6 @@ import {convertDependencies, reflectDependencies} from './util'; * `ngInjectableDef` onto the injectable type. */ export function compileInjectable(type: Type, srcMeta?: Injectable): void { - // Allow the compilation of a class with a `@Injectable()` decorator without parameters - const meta: Injectable = srcMeta || {providedIn: null}; - let def: any = null; // if NG_INJECTABLE_DEF is already defined on this class then don't overwrite it @@ -34,6 +31,7 @@ export function compileInjectable(type: Type, srcMeta?: Injectable): void { Object.defineProperty(type, NG_INJECTABLE_DEF, { get: () => { if (def === null) { + // Allow the compilation of a class with a `@Injectable()` decorator without parameters const meta: Injectable = srcMeta || {providedIn: null}; const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) || isUseValueProvider(meta) || isUseExistingProvider(meta); @@ -42,6 +40,7 @@ export function compileInjectable(type: Type, srcMeta?: Injectable): void { const compilerMeta: R3InjectableMetadataFacade = { name: type.name, type: type, + typeArgumentCount: 0, providedIn: meta.providedIn, ctorDeps: reflectDependencies(type), userDeps: undefined