Skip to content

fix(compiler): guard unverified ctor parameter types in class metadata - #70794

Open
mattrbeck wants to merge 1 commit into
angular:mainfrom
mattrbeck:fix-compiler-setclassmetadata-ts-ignore
Open

mattrbeck wants to merge 1 commit into
angular:mainfrom
mattrbeck:fix-compiler-setclassmetadata-ts-ignore

Conversation

@mattrbeck

@mattrbeck mattrbeck commented Sep 18, 2026

Copy link
Copy Markdown
Member

What is the current behavior?

In local compilation (or single-file compilation without whole-program type information), the compiler emits references to constructor dependency symbols into value positions in setClassMetadata without being able to confirm that they exist at runtime.

While the generated factory (傻fac) already guards unverified references with @ts-ignore, the ctorParameters callback in setClassMetadata does not. When downstream TypeScript compilation runs, type-only symbols (e.g. interfaces) emitted into the ctorParameters callback fail with compile errors (TS2339, TS2693, TS2708).

What is the new behavior?

Constructor parameters can now be provided to @angular/compiler in a structured format (R3ClassMetadataCtorParameter[]) in addition to pre-built expressions. When provided in structured form, the compiler generates the ctorParameters callback and attaches a @ts-ignore directly to the type property assignment of unverified parameters.

  • Full AOT compilation resolves all symbols, so valueUnverified is false and output remains bit-for-bit unchanged.
  • Partial declarations pass allowSuppressions: false and are never guarded.
  • Callers with pre-built callbacks (such as the linker) continue passing an o.Expression, which is emitted untouched.

Notes for reviewers

  • Comment on type property vs callback: @ts-ignore only suppresses errors on the immediately following line. A guard above the callback would fail to reach later parameters if decorators or arguments span multiple lines. Attaching directly to the type property assignment ensures the suppression stays on the type reference line regardless of multi-line object formatting.
  • Structured metadata in compiler: Taking R3ClassMetadataCtorParameter[] in @angular/compiler allows compiler consumers to share canonical parameter metadata lowering without duplicating callback AST construction or comment trivia logic.
  • Idempotency: The comment is attached to the fresh property assignment constructed during emit rather than mutating caller-owned type expressions, avoiding comment accumulation across incremental rebuilds.

@angular-robot angular-robot Bot added the area: compiler Issues related to `ngc`, Angular's template compiler label Sep 18, 2026
@ngbot ngbot Bot added this to the Backlog milestone Sep 18, 2026
@mattrbeck
mattrbeck requested a review from crisbeto September 18, 2026 01:05
@mattrbeck
mattrbeck marked this pull request as ready for review September 18, 2026 01:05
@mattrbeck mattrbeck added the target: patch This PR is targeted for the next patch release label Sep 18, 2026
(typeof ngDevMode === "undefined" || ngDevMode) && $i0$.傻setClassMetadata(CustomInjectable, [{
type: Injectable
}], () => [{
}], //@ts-ignore

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wouldn't we have to put this on top of type: Service?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Updated to place it right on the type: Service assignment

* This prevents hard compile errors (TS2339, TS2693, TS2708) in single-file or downstream
* typechecking environments when type-only symbols (e.g. interfaces) appear in value position.
*/
function compileCtorParameters(ctorParameters: o.Expression | null): o.Expression {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to do this when metadata.ctorParameters is constructed for the first time?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

metadata.ctorParameters is constructed in angular/compiler-cli today. ngp calls compileClassMetadata and doesn't go through the cli, so I think we have to do the work to inspect the AST here, unfortunately :/ In any case, I've updated it to apply directly to the type: Service rather than the map overall!

@mattrbeck
mattrbeck force-pushed the fix-compiler-setclassmetadata-ts-ignore branch from a2b7b5c to 9327537 Compare September 18, 2026 22:41
@mattrbeck mattrbeck changed the title fix(compiler): guard setClassMetadata ctorParameters with @ts-ignore fix(compiler): guard unverified ctor parameter types in class metadata Sep 18, 2026
@mattrbeck
mattrbeck force-pushed the fix-compiler-setclassmetadata-ts-ignore branch 6 times, most recently from 1c2c5ab to 9ceb996 Compare September 19, 2026 15:44
In local compilation mode the compiler can emit a reference to a symbol that it
was not able to prove has a value at runtime, because deciding that can require
information from other files. The generated factory already guards those
references with a `@ts-ignore`, but the `ctorParameters` callback passed to
`setClassMetadata` does not, so the same symbol ends up guarded in one place and
unguarded a few lines later in the same file. Type checking the generated code
then fails on the unguarded reference.

Constructor parameters are now handed to `@angular/compiler` in a structured
form rather than as an already-built callback, which lets the compiler attach a
suppression directly to the `type` property assignment of each parameter that
needs one. Only references that reflection could not verify are guarded: full
compilation resolves every reference, so its output is unchanged, and partial
declarations are published as JavaScript and never type checked, so they are
never guarded either.

Producers that build the callback themselves keep passing an expression, which
is emitted untouched.
@mattrbeck
mattrbeck force-pushed the fix-compiler-setclassmetadata-ts-ignore branch from 9ceb996 to 7729e4c Compare September 19, 2026 19:47
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 target: patch This PR is targeted for the next patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants