Skip to content

fix(forms): support generic unions in signal form schemas#67304

Open
alxhub wants to merge 1 commit intoangular:mainfrom
alxhub:sf-fix-generics
Open

fix(forms): support generic unions in signal form schemas#67304
alxhub wants to merge 1 commit intoangular:mainfrom
alxhub:sf-fix-generics

Conversation

@alxhub
Copy link
Member

@alxhub alxhub commented Feb 25, 2026

This commit resolves an issue where using an uninstantiated generic type parameter in a signal form model caused TypeScript compilation failures due to distributive conditional types (#66596). The previous attempt to fix this issue by tuple-wrapping everything caused another bug (#65535) that prevented property access on generic unions.

This commit balances the need to resolve nested generic property access while handling infinitely recursive generic structures without depth errors.

What changed and why:

  • Base State Wrappers: Tuple wrappers ([TModel] extends [AbstractControl]) are applied to FieldTreeBase to safely defer generic evaluation. This prevents primitive unions (like boolean) from incorrectly evaluating to never.
  • Naked Map Over Children: Object subfield checks (TModel extends Record) are re-evaluated as purely naked conditionals. Eager distribution over generics allows users to directly access shared properties of unresolved union types.
  • Array Interface Deflection: ReadonlyArrayLike<T> generic abstraction is redefined as an explicit interface instead of a mapped Pick type alias. This optimally intercepts TypeScript from eagerly evaluating infinitely recursive array structures (e.g. RecursiveType = (number | RecursiveType)[]).
  • Overloaded Context Methods: FieldNodeContext.stateOf and fieldTreeOf are defined as explicitly overloaded class methods and lexically bound (this) in the constructor. These changes are required to safely align the runtime bindings with the tautological conditionals implemented in the RootFieldContext interface structure.

Fixes #65535

@ngbot ngbot bot added this to the Backlog milestone Feb 25, 2026
This commit resolves an issue where using an uninstantiated generic type
parameter in a signal form model caused TypeScript compilation failures due to
distributive conditional types (angular#66596). The previous attempt to fix this issue
by tuple-wrapping everything caused another bug (angular#65535) that prevented property
access on generic unions.

This commit balances the need to resolve nested generic property access while
handling infinitely recursive generic structures without depth errors.

What changed and why:
- Base State Wrappers: Tuple wrappers (`[TModel] extends [AbstractControl]`) are
  applied to `FieldTreeBase` to safely defer generic evaluation. This prevents
  primitive unions (like `boolean`) from incorrectly evaluating to `never`.
- Naked Map Over Children: Object subfield checks (`TModel extends Record`) are
  re-evaluated as purely naked conditionals. Eager distribution over generics
  allows users to directly access shared properties of unresolved union types.
- Array Interface Deflection: `ReadonlyArrayLike<T>` generic abstraction is
  redefined as an explicit `interface` instead of a mapped `Pick` type alias.
  This optimally intercepts TypeScript from eagerly evaluating infinitely
  recursive array structures (e.g. `RecursiveType = (number | RecursiveType)[]`).
- Overloaded Context Methods: `FieldNodeContext.stateOf` and `fieldTreeOf` are
  defined as explicitly overloaded class methods and lexically bound (`this`) in
  the constructor. These changes are required to safely align the runtime bindings
  with the tautological conditionals implemented in the `RootFieldContext`
  interface structure.

Fixes angular#65535
@pullapprove pullapprove bot requested review from atscott and kirjs February 25, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Signal forms: Generics don't work the same as regular objects

1 participant