You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(safe-nav): version-aware optional chaining for Angular v22+ (#317) (#330)
Angular v22 changed the safe-navigation operator (`?.`) in template
expressions to yield `undefined` via native optional chaining, gated by
the `legacyOptionalChaining` compiler option. OXC unconditionally emitted
the legacy `== null ? null` ternary, so v22+ projects got the wrong
runtime value for any `?.` expression.
Changes:
- Add `legacyOptionalChaining` to `TransformOptions` (NAPI + Rust) and
thread it through ingest into the compilation jobs. The effective default
is derived from `angularVersion`: legacy for < v22, modern (native `?.`)
for >= v22, and legacy when the version is unknown (matches Angular's
conservative fallback).
- Add an `optional` flag to the resolved IR read/call nodes
(`ResolvedPropertyRead`/`ResolvedKeyedRead`/`ResolvedCall`) and pass it
through reify so it renders as native `?.` / `?.[]` / `?.()`.
- Rewrite `expand_safe_reads` to branch per node: legacy builds the
`SafeTernary` (`== null ? null`); modern rewrites each safe access into
the equivalent optional resolved read (no temporaries needed).
- Support the `$safeNavigationMigration(...)` escape hatch: a wrapped
subtree is forced back to legacy null semantics even on a modern target,
and the wrapper is stripped.
Two deviations from the issue text, both to match the reference compiler
(angular/angular@2896c93cc1):
- The modern form is native optional chaining (`ctx.user?.name`), not the
`== null ? undefined` ternary the issue described. Both yield `undefined`
at runtime; native `?.` matches Angular's emitted output.
- The magic function shipped in v22 is `$safeNavigationMigration(...)`, not
`$null(...)` (the commit message named `$null` but the code renamed it).
Partial/linker output keeps legacy semantics for now; threading the facade
field through partial emit is deferred (issue required-work #4).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 commit comments