Repository navigation
fmt: accept struct-update spread in object literals (B-900) - #4364
Conversation
`baml fmt` rejected any object literal containing a `...spread` member:
error: while formatting: Expected token/node OBJECT_FIELD or R_BRACE,
but found SPREAD_ELEMENT
The syntax parses and type-checks fine, so `baml check` and `baml test`
both passed on files that `baml fmt` refused to format at all.
`ObjectInitializer::from_cst` only admitted OBJECT_FIELD nodes. Introduce
an `ObjectMember` enum (Field | Spread) plus a `SpreadElement` strong-AST
node, and widen the object-literal member list to hold either.
Member order is semantic — later members win at runtime, so
`P { score: 9, ...p }` and `P { ...p, score: 9 }` evaluate differently.
Members are kept in a single ordered list so source order round-trips
verbatim; `...` is printed tight against its operand.
Scoped to OBJECT_LITERAL, the only node where the parser emits
SPREAD_ELEMENT (`parse_object_literal_body` is its sole caller). Map and
array literals reject spread at parse time, so they keep using
ObjectField directly.
Tests: single-line, multi-line, comment retention, spread of a call
expression, and order preservation across four spread/field arrangements
— each asserting idempotency.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017WeFQbEGpSJjzM79LHJ8du
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
📝 WalkthroughWalkthroughThe formatter adds ChangesObject spread formatting
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant ObjectInitializerParser
participant ObjectMember
participant SpreadElementPrinter
ObjectInitializerParser->>ObjectMember: Parse field or spread element
ObjectMember->>SpreadElementPrinter: Format spread expression
SpreadElementPrinter-->>ObjectMember: Return formatted ...expr
ObjectMember-->>ObjectInitializerParser: Preserve ordered members
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Silences clippy::doc_markdown (pedantic, warn-by-default in this workspace) on the new object_spread_tests module. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_017WeFQbEGpSJjzM79LHJ8du
Both failures on the previous run were infrastructure flakes unrelated to
this change (which only touches baml_fmt):
- Release bridge_cffi (x86_64-unknown-linux-gnu): runner TLS failure in
the git checkout step, before any code was built.
- cargo test (aarch64-apple-darwin): random SIGABRT in a bex_engine
async-runtime test. canary hits the same job with a different random
bex_engine SIGABRT, and the test passes 12/12 locally on macOS.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017WeFQbEGpSJjzM79LHJ8du
Binary size checks passed✅ 7 passed
Generated by |
Fixes B-900.
Problem
baml fmtrejected any object literal containing a...spreadmember:The syntax parses and type-checks, so
baml checkandbaml testpassed on filesbaml fmtrefused to format at all. Two-line repro:Cause
ObjectInitializer::from_cstinbaml_fmtonly admittedOBJECT_FIELDnodes, so aSPREAD_ELEMENTsibling fell through to the catch-all error arm.Fix
Added an
ObjectMemberenum (Field | Spread) and aSpreadElementstrong-AST node, and widened the object-literal member list to hold either.Members stay in one ordered list rather than being split into "spreads" and "fields", because order is semantic — later members win at runtime, so
P { score: 9, ...p }evaluates to1whileP { ...p, score: 9 }evaluates to9. Splitting them would silently change program behavior....prints tight against its operand.The change is scoped to
OBJECT_LITERAL, the only node where the parser emitsSPREAD_ELEMENT(parse_object_literal_bodyis its sole caller). Map and array literals reject spread at parse time, so they keep usingObjectFielddirectly.Tests
Four regression tests in
object_spread_tests, each asserting idempotency:P { ...p, score: 1 }...p, score,score, ...p,...a, ...b, and...a, score, ...b...base()), not just a bare nameFull suite: 124 passed, 0 failed — 120 pre-existing plus these 4, including the 1024-case
formatter_scenario_matrix.Verification
Formatting a messy input now produces stable output, and a second pass is a no-op:
Also confirmed against the case that surfaced this: Example 2 of
plugins/baml/skills/core/SKILL.mdin thebaml-skillrepo now passesbaml fmt,baml check, andbaml test. That example demonstrates spread while the same skill instructs agents to runbaml fmt, so the guidance was self-contradicting until this landed.🤖 Generated with Claude Code
https://claude.ai/code/session_017WeFQbEGpSJjzM79LHJ8du
Note
Low Risk
Formatter-only change scoped to object-literal AST/printing; no language semantics, auth, or data-handling paths are modified.
Overview
baml fmtnow formats object literals that contain...spreadmembers, which previously failed with an unexpectedSPREAD_ELEMENTerror even though the syntax already parsed and type-checked.ObjectInitializermembers are modeled as an orderedObjectMemberlist (Field | Spread) so field/spread order is preserved — later members win at runtime. Spreads print tight against their operand (...expr), including call expressions, on both single- and multi-line layouts.Adds regression tests covering order preservation, multi-line comments, call operands, and formatter idempotency.
Reviewed by Cursor Bugbot for commit e9058c1. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
...expression) in object initializers.Bug Fixes