Skip to content

Type-check a Session assignment against its binding - #4531

Merged
antoniosarosi merged 3 commits into
canaryfrom
antonio/session-assignment-typecheck
Aug 19, 2026
Merged

Type-check a Session assignment against its binding#4531
antoniosarosi merged 3 commits into
canaryfrom
antonio/session-assignment-typecheck

Conversation

@antoniosarosi

@antoniosarosi antoniosarosi commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

In a Session (the notebook), assigning to a variable skipped type checking entirely. This compiled:

s.eval(#"let n = 5"#);
s.eval(#"n = "seven""#);   // no error!
s.eval(#"n.abs()"#);        // VM crash you cannot catch

In ordinary BAML, n = "seven" on an int is a compile error. Now sessions behave the same way:

s.eval(#"n = "seven""#);
// error[E0001]: mismatched types — expected int, found string   (identical to ordinary code)

s.eval(#"let n = "seven""#);   // still fine — re-declaring at a new type is how you change it
s.eval(#"n += 1"#);            // compound assignments checked the same way

How

Instead of adding a second checker, the session rewrite now emits the assignment as an assignment (let __target = <the binding>; __target = value), so the ordinary checker runs and the error message is the ordinary one by construction — same code, same wording, byte for byte (pinned by tests that compile the same program both ways and compare).

Notes

  • The value is spliced in unwrapped because parentheses change verdicts in ordinary BAML too: n += 1.5 on an int compiles (and panics at runtime — a pre-existing ordinary-code hole), while n += (1.5) is a compile error. Sessions now match both behaviors exactly, including the hole. Before this PR, a session's n += 1.5 produced 6.5 by accident; now it lands where ordinary code lands.
  • The synthesized local uses a name (__baml_assign_...) that no user binding can ever collide with — during review we found that a user variable literally named target_1 could silently capture the old name and turn an assignment into a no-op.

@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview Aug 19, 2026 6:44pm
promptfiddle2 Ready Ready Preview Aug 19, 2026 6:44pm

Request Review

@github-actions

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cec60ce-094f-482f-abcd-e2ece28f7b57

📥 Commits

Reviewing files that changed from the base of the PR and between 55f6420 and 053f52f.

📒 Files selected for processing (3)
  • baml_language/CHANGELOG.md
  • baml_language/crates/baml_tests/tests/runtime_session.rs
  • baml_language/crates/bex_project/src/runtime_compile.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • baml_language/CHANGELOG.md

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

Session assignments now use uniquely named typed temporary locals. Rejected assignments match ordinary BAML behavior and preserve bindings. Regression tests cover shadowing, compound assignments, numeric operands, structured values, and same-type updates.

Changes

Session assignment semantics

Layer / File(s) Summary
Collision-safe assignment lowering
baml_language/crates/bex_project/src/runtime_compile.rs
Visible binding assignments use a __baml_assign_* temporary name before applying and committing the assignment.
Assignment compatibility and behavior tests
baml_language/crates/baml_tests/tests/runtime_session.rs, baml_language/CHANGELOG.md
Tests compare Session diagnostics with ordinary BAML and verify rejected assignments, value preservation, shadowing, compound operations, value shapes, and same-type updates. The changelog records the compound-assignment behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🟡 Moderate · up to 053f5

This change updates Rust runtime compilation behavior, and the required library test command has not been explicitly confirmed; merge should wait for that check or explicit owner acceptance.

Sequence Diagram(s)

sequenceDiagram
  participant reflect.Session
  participant runtime_compile
  participant GlobalBinding
  reflect.Session->>runtime_compile: lower visible assignment
  runtime_compile->>runtime_compile: validate through typed temporary
  runtime_compile->>GlobalBinding: commit valid assignment
  runtime_compile-->>reflect.Session: return runtime result or failure
Loading

Poem

A rabbit checks each binding twice,
Typed paws prevent a bad surprise.
Colliding names now hop away,
Good assignments safely stay.
Tests confirm the path is right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the pull request's main change: type-checking Session assignments against binding types.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch antonio/session-assignment-typecheck

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@vercel
vercel Bot temporarily deployed to Preview – beps August 19, 2026 16:21 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 August 19, 2026 16:29 Inactive
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

Binary size checks passed

7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
baml-cli Linux 🔒 31.8 MB 12.6 MB file 31.7 MB +134.2 KB (+0.4%) OK
packed-program Linux 🔒 25.0 MB 9.2 MB file 24.9 MB +149.2 KB (+0.6%) OK
baml-cli macOS 🔒 25.5 MB 11.2 MB file 25.5 MB +65.4 KB (+0.3%) OK
packed-program macOS 🔒 20.7 MB 8.2 MB file 20.6 MB +190.6 KB (+0.9%) OK
baml-cli Windows 🔒 27.3 MB 11.4 MB file 27.2 MB +132.5 KB (+0.5%) OK
packed-program Windows 🔒 21.8 MB 8.3 MB file 21.7 MB +125.4 KB (+0.6%) OK
bridge_wasm WASM 21.4 MB 🔒 5.4 MB gzip 5.3 MB +66.4 KB (+1.2%) OK

🔒 = the size this artifact is GATED on (ceiling + delta). Binaries gate on file size (installed binary); WASM gates on gzip (download size). The other size is shown for information only.


Generated by cargo size-gate · workflow run

@antoniosarosi

Copy link
Copy Markdown
Contributor Author

Review round applied — blocker fixed, both hardenings in, changelog corrected off a measurement. Pushed as 053f52f.

Blocker — the synthesized local could collide with a user binding

Reproduced against this branch before fixing it. A user binding mints
__baml_session_{seq}_{name}, so a binding literally called target_1 mints
__baml_session_N_target_1 — exactly what the per-statement local was named when the
assignment was statement 1 of submission N. The block-local then shadows the global, and the
rewritten value reads its own copy:

s.eval("let n = 5")
s.eval("let target_1 = 99\nn = target_1")
s.eval<int>("n")
answer
canary 99
this branch, before the fix 5 — silent, no diagnostic
this branch, after 99

Fixed as suggested: the local is now __baml_assign_{sequence}_{index}, a root prefix
internal() cannot produce, so no user name can ever land on it. The submission above is
pinned as a fixture (a_session_assignment_does_not_shadow_a_user_binding), and I checked the
neighbouring shapes while I was there — same name across two submissions, and an ordinary name
in one submission — both already correct, and still correct.

Non-blocking, same round

Changelog — measured rather than assumed. I ran the n += 1.5 shape on true canary
(git show origin/canary:…runtime_compile.rs) rather than on this branch with the change
stashed, because HEAD already carried it. Canary answers 6.5: the float really was committed
into the int binding and read back, so this is a live behavior change, and the changelog now
says so — "used to commit the float and read back 6.5, and now compiles and fails at runtime
exactly as the same line does in ordinary BAML."

Both test hardenings. a_session_assignment_is_no_stricter_than_ordinary_code now asserts
all three halves: the ordinary spelling compiles, the parenthesized spelling does not
(E0001: mismatched types: expected int, found float`` — the freshness quirk the splice
avoids, so the tripwire can actually trip), and the ordinary program fails at RUNTIME, which is
where the Session now lands too.

Span remapping filed as MIG_BRIEF Fix 12: session diagnostics carry generated-module
offsets under the submission's own name, nothing maps them back, and no test asserts
e.diagnostics[0].span at all. Pre-existing, measurably worsened here (one line became four),
with the fix sketched — lower_session_submission already computes each statement's
source_range; it just never reaches owned_diagnostic.

Gate

Full pinned gate on the final tree: 3,864/3,864 passed, 24 skipped, no unreferenced
snapshots, doctests clean (1,447 s). CI-mirror extras (this touches bex_project, out of gate)
5,200/5,200 (694 s); RUSTDOCFLAGS="-D warnings" cargo doc --all --no-deps clean;
cargo fmt --all --check and cargo clippy --all-targets --all-features clean. Caps 24/24,
pinned 1.93.0, CARGO_INCREMENTAL=0. Not enqueued.

@vercel
vercel Bot temporarily deployed to Preview – beps August 19, 2026 18:36 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 August 19, 2026 18:44 Inactive
@blacksmith-sh

This comment has been minimized.

@antoniosarosi
antoniosarosi added this pull request to the merge queue Aug 19, 2026
Merged via the queue into canary with commit 1a5384b Aug 19, 2026
132 of 134 checks passed
@antoniosarosi
antoniosarosi deleted the antonio/session-assignment-typecheck branch August 19, 2026 19:13
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.

1 participant