fix(compiler-cli): error for type parameter declarations

Fixes an error that was heppning when a generic param has type parameters of its own. There were a few different issues going on:
1. In #67707 I had changed a bit how we pass the `genericContextBehavior` which ended up ignoring the `useContextGenericType` option from the environment.
2. All directives depend on themselves, but we were overridding the `genericContextBehavior` for the directive being processed.
3. The type translator wasn't handling type parameter declarations. Technically we shouldn't be able to hit a code path that has a type parameter, however it's also easy enough to handle so we might as well.

Relates to #67704.

(cherry picked from commit ab061a7610)
This commit is contained in:
Kristiyan Kostadinov 2026-04-07 09:09:04 +02:00 committed by Andrew Scott
parent e40d378f3e
commit 2c6781071f
3 changed files with 50 additions and 17 deletions

View file

@ -251,14 +251,16 @@ class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor {
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, 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}`,
);
}
}

View file

@ -121,7 +121,11 @@ export function adaptTypeCheckBlockMetadata(
...adaptGenerics(
dir.ref.node as ClassDeclaration<ts.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<ClassDeclaration>),
...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);

View file

@ -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<T> = {prop: T};
@Directive({
host: {
'[class.some-class]': 'foo || bar' // Only necessary to enable type checking.
},
})
export class TestDir<T, U = T extends Foo<infer V> ? V : never> {
foo?: T;
bar?: U;
}
`,
);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(0);
});
});
describe('InjectorDef emit optimizations for standalone', () => {