Skip to content

refactor(compiler-cli): abstract type check block metadata to be AST-free#67109

Draft
atscott wants to merge 1 commit intoangular:mainfrom
atscott:whatishappening
Draft

refactor(compiler-cli): abstract type check block metadata to be AST-free#67109
atscott wants to merge 1 commit intoangular:mainfrom
atscott:whatishappening

Conversation

@atscott
Copy link
Contributor

@atscott atscott commented Feb 17, 2026

This commit refactors the template type checking metadata interfaces to use detached, serializable metadata rather than retaining direct references to ts.Node or ts.Declaration instances.

A new tcb_adapter translates traditional TypeScript AST-bound metadata into these decoupled structures. This abstraction lays the groundwork for supporting native preprocessors (such as Rust or ts-go) which serialize metadata over JSON rather than passing live TypeScript objects.

Key changes:

  • Introduced TcbDirectiveMetadata, TcbComponentMetadata, TcbReferenceMetadata, and TcbPipeMetadata to replace TypeCheckableDirectiveMeta where appropriate.
  • Substituted deep TS compilation AST references with string module names and source spans to preserve out-of-band diagnostic capabilities.
  • Detached generic typeParameters and transformType properties into synthesized, standalone TS mappings.
  • Updated generateTypeCheckBlock and corresponding Operations to consume the new metadata.

@angular-robot angular-robot bot added the area: compiler Issues related to `ngc`, Angular's template compiler label Feb 17, 2026
@ngbot ngbot bot added this to the Backlog milestone Feb 17, 2026
…free

This commit refactors the template type checking metadata interfaces to use detached, serializable metadata rather than retaining direct references to ts.Node or ts.Declaration instances.

A new tcb_adapter translates traditional TypeScript AST-bound metadata into these decoupled structures. This abstraction lays the groundwork for supporting native preprocessors (such as Rust or ts-go) which serialize metadata over JSON rather than passing live TypeScript objects.

Key changes:
- Introduced TcbDirectiveMetadata, TcbComponentMetadata, TcbReferenceMetadata, and TcbPipeMetadata to replace TypeCheckableDirectiveMeta where appropriate.
- Substituted deep TS compilation AST references with string module names and source spans to preserve out-of-band diagnostic capabilities.
- Detached generic typeParameters and transformType properties into synthesized, standalone TS mappings.
- Updated generateTypeCheckBlock and corresponding Operations to consume the new metadata.
@atscott atscott added requires: TGP This PR requires a passing TGP before merging is allowed labels Feb 17, 2026
if (this.typeCtors.has(node)) {
return this.typeCtors.get(node)!;
typeCtorFor(dir: TcbDirectiveMetadata): ts.Expression {
const key = dir.ref.moduleName ? `${dir.ref.moduleName}#${dir.ref.name}` : dir.ref.name;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, both moduleName and class names are not exactly unique. moduleName is also not defined for relative-path imports, so we risk colliding class names across multiple files.

A better option: key by absolute file path + '#' + position offset of the class in the source file. Much more reliable. We should extract that computation to a utility file/method and give it a branded type for safety.

name = emitted.expression.value.name!;
moduleName = emitted.expression.value.moduleName;
isLocal = false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we throw/fail if emitted.kind is not Success?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is due to computing this up-front. In the previous code, env.referenceType(this.dir.ref) or env.referenceExternalType(this.dir.ref) happened inside operations and would only happen if that specific component's TCB evaluation reached a point where the reference was required.

Now we are translating everything up front to strip TS AST references.

let type: ts.TypeNode;
let span: ParseSourceSpan;
if (this.dir.isGeneric === false || dirRef.node.typeParameters === undefined) {
if (this.dir.isGeneric === false || this.dir.typeParameters?.length === 0) {
Copy link
Member

@alxhub alxhub Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail the condition if this.dir.typeParameters is undefined. It needs to be (this.dir.typeParameters?.length ?? 0) === 0. Better yet, make typeParameters required (I almost always avoid optional members of metadata objects for this reason).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, yea I was trying to revert these changes but missed one...

meta: TypeCheckBlockMetadata,
env: Environment,
): {tcbMeta: TcbTypeCheckBlockMetadata; component: TcbComponentMetadata} {
const dirCache = new Map<any, TcbDirectiveMetadata>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No any.

}
return target as ReferenceTarget<TcbDirectiveMetadata>;
},
getDeferredTriggerTarget: (b: any, t: any) => meta.boundTarget.getDeferredTriggerTarget(b, t),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of anys.

* provided.
*/
referenceExternalType(moduleName: string, name: string, typeParams?: Type[]): ts.TypeNode {
referenceExternalType(moduleName: string | null, name: string, typeParams?: Type[]): ts.TypeNode {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The | null is odd here - external types should always have a module name we're trying to reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: compiler Issues related to `ngc`, Angular's template compiler requires: TGP This PR requires a passing TGP before merging is allowed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants