Skip to content

fix: preserve object and array spread syntax in template bindings#260

Merged
Brooooooklyn merged 3 commits intovoidzero-dev:mainfrom
ashley-hunter:feat/fix-spread-syntax-in-template-bindings
May 5, 2026
Merged

fix: preserve object and array spread syntax in template bindings#260
Brooooooklyn merged 3 commits intovoidzero-dev:mainfrom
ashley-hunter:feat/fix-spread-syntax-in-template-bindings

Conversation

@ashley-hunter
Copy link
Copy Markdown
Contributor

Summary

Object and array spread expressions in Angular template bindings were silently dropped during compilation with no error or warning:

<!-- Before: spread lost, emits { extra: 'val' } -->
<div [prop]="{ ...base, extra: 'val' }"></div>

<!-- After: correctly emits pureFunction1(_c0, ctx.base) where _c0 = (a0) => ({ ...a0, extra: 'val' }) -->
<div [prop]="{ ...base, extra: 'val' }"></div>

The root cause was that LiteralMapKey::Spread was skipped in ingest.rs during AST→IR conversion, and no subsequent pipeline stage had any representation for spread entries. Array spread ([...a, 'val']) had the same gap in angular_expression.rs.

Changes

The fix spans the full compilation pipeline:

  • ir/expression.rs: Added spreads: Vec<bool> parallel field to IrLiteralMapExpr, DerivedLiteralMapExpr, IrLiteralArrayExpr, DerivedLiteralArrayExpr to track which entries are spreads
  • pipeline/ingest.rs: Handle LiteralMapKey::Spread when converting AST→IR (previously skipped with a comment)
  • pipeline/phases/resolve_names.rs: Propagate spreads through name resolution
  • pipeline/phases/pure_literal_structures.rs: Emit spread entries in pure function wrappers
  • pipeline/phases/reify/ir_expression.rs: Emit spread entries when lowering IR→output AST
  • pipeline/phases/reify/angular_expression.rs: Handle SpreadElement in LiteralArray arm
  • pipeline/emit.rs: Fix convert_ast_for_pure_function_body to handle spreads in both LiteralMap and LiteralArray
  • output/ast.rs: clone_in correctly copies is_spread on LiteralMapEntry
  • output/emitter.rs: Emit ...expr for spread entries in both object and array literals
  • output/oxc_converter.rs: Handle SpreadProperty in object expressions (previously skipped)
  • pipeline/phases/chaining.rs: Preserve is_spread when cloning entries during chaining
  • pipeline/phases/defer_configs.rs, pipe_variadic.rs: Maintain spreads parallel-vec invariant

Tests

  • 9 new Rust snapshot tests: object spread, array spread, multiple spreads, spread-with-pipe, spread-at-end, chained bindings, arrow function body spread
  • 1 new unit test in oxc_converter.rs for the SpreadProperty path
  • 2 new TypeScript NAPI tests for object and array spread

Output verified against official Angular 21.2.11 compiler — functionally identical (pure function wrapping, parameter naming, binding instructions).

Object and array spread expressions in Angular template bindings were
silently dropped during compilation, e.g. `[prop]="{ ...base, key: val }"``
would emit `{ key: val }` with the spread lost entirely.

The fix spans the full compilation pipeline:
- ingest: handle LiteralMapKey::Spread when converting AST→IR
- ir/expression: add spreads: Vec<bool> parallel field to all literal
  map/array IR types to track which entries are spreads
- resolve_names: propagate spreads through name resolution
- pure_literal_structures: emit spread entries in pure function wrappers
- reify/ir_expression: emit spread entries when lowering IR→output AST
- reify/angular_expression: handle SpreadElement in LiteralArray arm
- emit: fix convert_ast_for_pure_function_body to handle spreads in
  both LiteralMap and LiteralArray
- output/ast: clone_in and chaining preserve is_spread on LiteralMapEntry
- output/oxc_converter: handle SpreadProperty in object expressions
- defer_configs, pipe_variadic: maintain spreads parallel-vec invariant

Adds 9 snapshot tests covering object spread, array spread, multiple
spreads, spread-with-pipe, spread-at-end, chained bindings, and arrow
function body spread. Adds TS NAPI tests for both object and array spread.
@Brooooooklyn
Copy link
Copy Markdown
Member

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 936c302549

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ashley-hunter and others added 2 commits May 5, 2026 14:22
Pooled pure-function bodies for `[a]` and `[...a]` (and object equivalents)
collided in the constant pool because the key generation for
DerivedLiteralArray/DerivedLiteralMap ignored the `spreads` parallel array,
causing one binding to silently inherit the other's runtime semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Brooooooklyn Brooooooklyn merged commit 34a4669 into voidzero-dev:main May 5, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants