diff --git a/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts index 52eb0546392..c1f5e4e3f92 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts @@ -251,14 +251,16 @@ class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor { visitWrappedNodeExpr(ast: o.WrappedNodeExpr, context: Context): ts.TypeNode { const node: ts.Node = ast.node; if (ts.isEntityName(node)) { - return ts.factory.createTypeReferenceNode(node, /* typeArguments */ undefined); + return ts.factory.createTypeReferenceNode(node); } else if (ts.isTypeNode(node)) { return node; } else if (ts.isLiteralExpression(node)) { return ts.factory.createLiteralTypeNode(node); + } else if (ts.isTypeParameterDeclaration(node)) { + return ts.factory.createTypeReferenceNode(node.name); } else { throw new Error( - `Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]}`, + `Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]} in ${node.getSourceFile()?.fileName}`, ); } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/tcb_adapter.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/tcb_adapter.ts index 3be223c35d6..d62de049511 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/tcb_adapter.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/tcb_adapter.ts @@ -121,7 +121,11 @@ export function adaptTypeCheckBlockMetadata( ...adaptGenerics( dir.ref.node as ClassDeclaration, env, - TcbGenericContextBehavior.UseEmitter, + // The directive that we're processing is its own dependency + // so we should the same generic context behavior. + extractRef(dir.ref).key === extractRef(ref).key + ? genericContextBehavior + : TcbGenericContextBehavior.UseEmitter, ), }; @@ -201,13 +205,7 @@ export function adaptTypeCheckBlockMetadata( }, component: { ref: extractRef(ref as Reference), - ...adaptGenerics( - ref.node, - env, - env.config.useContextGenericType - ? genericContextBehavior - : TcbGenericContextBehavior.FallbackToAny, - ), + ...adaptGenerics(ref.node, env, genericContextBehavior), }, }; } @@ -224,6 +222,10 @@ function adaptGenerics( let typeArguments: string[] | null; if (node.typeParameters !== undefined && node.typeParameters.length > 0) { + if (!env.config.useContextGenericType) { + genericContextBehavior = TcbGenericContextBehavior.FallbackToAny; + } + switch (genericContextBehavior) { case TcbGenericContextBehavior.UseEmitter: const emitter = new TypeParameterEmitter(node.typeParameters, env.reflector); diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 975b9a2bc7b..52b769ccede 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -10788,10 +10788,12 @@ runInEachFileSystem((os: string) => { expect(codes).toEqual([ngErrorCode(ErrorCode.NGMODULE_BOOTSTRAP_IS_STANDALONE)]); }); - it('should compile a component with a complex generic', () => { - env.write( - 'test.ts', - ` + [true, false].forEach((strictTemplates) => { + it(`[strictTemplates: ${strictTemplates}] should compile a component with a complex generic`, () => { + env.tsconfig({strictTemplates}); + env.write( + 'test.ts', + ` import {Component} from '@angular/core'; @Component({ @@ -10803,10 +10805,37 @@ runInEachFileSystem((os: string) => { TOptions extends { [K in keyof T]?: T[K] } = object > {} `, - ); + ); - const diags = env.driveDiagnostics(); - expect(diags.length).toBe(0); + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + + // See #67704. + it(`[strictTemplates: ${strictTemplates}] should compile a directive with a generic that has type parameters`, () => { + env.tsconfig({strictTemplates}); + env.write( + 'test.ts', + ` + import {Directive} from '@angular/core'; + + type Foo = {prop: T}; + + @Directive({ + host: { + '[class.some-class]': 'foo || bar' // Only necessary to enable type checking. + }, + }) + export class TestDir ? V : never> { + foo?: T; + bar?: U; + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); }); describe('InjectorDef emit optimizations for standalone', () => {