Skip to content

fix(tir): report type errors inside ${...} interpolation (B-836)#4096

Merged
codeshaunted merged 2 commits into
canaryfrom
avery/b-836
Jul 21, 2026
Merged

fix(tir): report type errors inside ${...} interpolation (B-836)#4096
codeshaunted merged 2 commits into
canaryfrom
avery/b-836

Conversation

@codeshaunted

@codeshaunted codeshaunted commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes B-836: type errors inside backtick ${...} interpolation were silently swallowed. baml check exited 0 on code like `result: ${[1,2,3].len()}`, then baml run crashed (on current canary: the runtime-lowering ICE guard in runtime_ty.rs; on older builds: a raw VM internal error: type error: expected map, got array).

Root cause

The sub-expressions were never "skipped" by the checker. The untagged backtick desugars into a string.from(...) concat tree that shares the original ${expr} nodes, and inferring that tree emits the real diagnostics at the right spans. The Expr::Template arm then called retain_user_name_diagnostics, discarding everything except UnresolvedName. That filter predates the string.from desugar: the old .to_string() wrapping could genuinely fail on synthetic spans, but string.from is total, so the filter only deleted the user's own errors and let Ty::Error reach MIR.

Changes

  • Keep every diagnostic from inferring the elaborated tree; delete the dead retain_user_name_diagnostics helper. The per-segment check still owns the one rule the tree cannot express (non-null interps), anchored on the original ${...} spans.
  • Dropping the filter exposed one real cascade: string.from<T> reported E0002 (cannot infer type parameter) when its argument had already failed to type. check_call_inner now suppresses CannotInferTypeParameter per parameter when the variable occurs in a param whose argument's recorded type carries an error-recovery sentinel (Error/Unknown); an argument only ever holds one through its own already-diagnosed failure. This also fixes the same double-report for any generic call with a broken argument, e.g. consume([1,2,3].len()).
  • Consolidated the hand-rolled contains_* walkers in generics.rs onto a shared contains_ty_where node-predicate traversal; contains_typevar, contains_typevar_where, and the new contains_error_recovery are one-liners over it.

Tests

Two new corpus projects, written first with baselines captured against the unfixed compiler:

  • diagnostic_errors/backtick_interp_type_errors: the ticket's literal repro plus chained calls, operator errors, unresolved names, and errors inside ${for}/${if} segment bodies. Before: only the unresolved name reported. After: all seven diagnostics, each at its original span, no cascades.
  • diagnostic_errors/generic_error_arg_no_cascade: errored-arg suppression, the bound-from-expected case, and a control pinning that a genuinely uninferable parameter (phantom<T>()) still reports E0002.

Full -p baml_tests run (2834 tests) passes with zero changes to existing snapshots; -p baml_compiler2_tir and the bex_engine backtick suites pass; clippy clean.

Note: enforce-baml-size is expected to fail (stale baseline, known team-wide issue since ~07-17).

Summary by CodeRabbit

  • Bug Fixes

    • Improved compiler diagnostics for backtick interpolations by preserving accurate, user-facing error locations (including within for/if segments).
    • Reduced cascading “cannot infer type parameter” errors when an argument already has error recovery, while still reporting genuine inference failures when recovery isn’t possible.
  • Tests

    • Added regression coverage for backtick interpolation type errors, unresolved names, and errors inside for/if bodies.
    • Added regression coverage for generic type-parameter inference suppression and error reporting behavior.

The untagged-backtick arm typed the elaborated `string.from` concat
tree, then discarded every resulting diagnostic except UnresolvedName.
That filter predates the `string.from` desugar: the old `.to_string()`
wrapping could genuinely fail on synthetic spans, but the current
wrapper is total, so the filter only swallowed the user's own errors
(member access, operators, calls) inside `${...}`. `baml check` passed,
and the `Ty::Error` recovery type flowed into MIR, where runtime
lowering ICEs (previously: a degraded type reached the VM as a raw
"type error: expected map, got array").

Keep every elaborated-tree diagnostic; they anchor on the original
`${...}` spans because the segment ExprIds are shared with the tree.
Delete the dead retain_user_name_diagnostics helper.

Dropping the filter exposed one real cascade: `string.from<T>` reported
"cannot infer type parameter" (E0002) when its argument had already
failed to type. Extend check_call_inner's cascade suppression per
parameter: skip CannotInferTypeParameter when the variable occurs in a
param whose argument's recorded type carries an error-recovery sentinel
(Error or Unknown); an argument only ever holds one through its own
already-diagnosed failure. Consolidate the hand-rolled contains_*
walkers in generics.rs onto a shared contains_ty_where and express
contains_typevar, contains_typevar_where, and the new
contains_error_recovery through it.
@linear

linear Bot commented Jul 21, 2026

Copy link
Copy Markdown

B-836

@vercel

vercel Bot commented Jul 21, 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, Comment Jul 21, 2026 1:11am
promptfiddle Ready Ready Preview, Comment Jul 21, 2026 1:11am
promptfiddle2 Ready Ready Preview, Comment Jul 21, 2026 1:11am

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 Jul 21, 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

Run ID: c94808e4-a4be-43ca-ba44-58e627b0a552

📥 Commits

Reviewing files that changed from the base of the PR and between cb85a3f and 5ca1e8e.

📒 Files selected for processing (1)
  • baml_language/crates/baml_compiler2_tir/src/generics.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • baml_language/crates/baml_compiler2_tir/src/generics.rs

📝 Walkthrough

Walkthrough

This change centralizes recursive type inspection, suppresses cascading generic inference diagnostics caused by prior errors, and preserves type errors from backtick interpolation expressions. Regression tests cover generic inference and interpolation segments.

Changes

Diagnostic reporting

Layer / File(s) Summary
Shared type-tree error detection
baml_language/crates/baml_compiler2_tir/src/generics.rs
Adds reusable recursive type predicates and error-recovery detection, while simplifying type-variable searches and testing traversal across nested type shapes.
Generic inference cascade suppression
baml_language/crates/baml_compiler2_tir/src/builder.rs, baml_language/crates/baml_tests/projects/diagnostic_errors/generic_error_arg_no_cascade/main.baml
Skips cannot-infer diagnostics when an associated argument already contains an error, while preserving diagnostics for genuinely uninferable parameters.
Backtick interpolation diagnostics
baml_language/crates/baml_compiler2_tir/src/builder.rs, baml_language/crates/baml_compiler2_tir/src/infer_context.rs, baml_language/crates/baml_tests/projects/diagnostic_errors/backtick_interp_type_errors/main.baml
Retains interpolation expression diagnostics, removes selective user-name retention, and tests errors across direct, chained, loop, and conditional interpolations.

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

Possibly related PRs

  • BoundaryML/baml#3577: Related backtick interpolation elaboration and diagnostic behavior.
  • BoundaryML/baml#3910: Related generic type-parameter inference and error-handling changes in the same builder path.

Poem

I’m a rabbit guarding types in the night,
Keeping true errors clear and bright.
Cascades hop away, interpolation speaks,
Generics find fewer misleading squeaks.
Tests nibble every troublesome case.

🚥 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 matches the main change: surfacing type errors inside backtick ${...} interpolations.
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 avery/b-836

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@baml_language/crates/baml_compiler2_tir/src/generics.rs`:
- Around line 170-230: Add a #[cfg(test)] module in generics.rs with focused
unit tests for contains_ty_where and contains_error_recovery, covering direct
matches, nested types, and non-matching types across the relevant Ty branches,
including function, collection, union, and generic argument structures. Use
existing Ty construction utilities and assert both positive and negative results
without changing the helper implementations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0b2357e4-d212-40ec-b7b0-1448ec5c4b05

📥 Commits

Reviewing files that changed from the base of the PR and between be5e7cd and cb85a3f.

⛔ Files ignored due to path filters (12)
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/backtick_interp_type_errors/baml_tests__diagnostic_errors__backtick_interp_type_errors__01_lexer__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/backtick_interp_type_errors/baml_tests__diagnostic_errors__backtick_interp_type_errors__02_parser__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/backtick_interp_type_errors/baml_tests__diagnostic_errors__backtick_interp_type_errors__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/backtick_interp_type_errors/baml_tests__diagnostic_errors__backtick_interp_type_errors__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/backtick_interp_type_errors/baml_tests__diagnostic_errors__backtick_interp_type_errors__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/backtick_interp_type_errors/baml_tests__diagnostic_errors__backtick_interp_type_errors__10_formatter__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/generic_error_arg_no_cascade/baml_tests__diagnostic_errors__generic_error_arg_no_cascade__01_lexer__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/generic_error_arg_no_cascade/baml_tests__diagnostic_errors__generic_error_arg_no_cascade__02_parser__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/generic_error_arg_no_cascade/baml_tests__diagnostic_errors__generic_error_arg_no_cascade__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/generic_error_arg_no_cascade/baml_tests__diagnostic_errors__generic_error_arg_no_cascade__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/generic_error_arg_no_cascade/baml_tests__diagnostic_errors__generic_error_arg_no_cascade__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/generic_error_arg_no_cascade/baml_tests__diagnostic_errors__generic_error_arg_no_cascade__10_formatter__main.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/generics.rs
  • baml_language/crates/baml_compiler2_tir/src/infer_context.rs
  • baml_language/crates/baml_tests/projects/diagnostic_errors/backtick_interp_type_errors/main.baml
  • baml_language/crates/baml_tests/projects/diagnostic_errors/generic_error_arg_no_cascade/main.baml
💤 Files with no reviewable changes (1)
  • baml_language/crates/baml_compiler2_tir/src/infer_context.rs

Comment thread baml_language/crates/baml_compiler2_tir/src/generics.rs
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

Binary size checks passed

7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
baml-cli Linux 🔒 25.3 MB 10.7 MB file 25.3 MB +12.9 KB (+0.1%) OK
packed-program Linux 🔒 17.0 MB 7.0 MB file 17.0 MB +4.3 KB (+0.0%) OK
baml-cli macOS 🔒 19.6 MB 9.3 MB file 19.5 MB +33.2 KB (+0.2%) OK
packed-program macOS 🔒 13.2 MB 6.2 MB file 13.2 MB +16.7 KB (+0.1%) OK
baml-cli Windows 🔒 21.1 MB 9.5 MB file 21.1 MB +30.2 KB (+0.1%) OK
packed-program Windows 🔒 14.2 MB 6.2 MB file 14.1 MB +11.0 KB (+0.1%) OK
bridge_wasm WASM 16.2 MB 🔒 4.4 MB gzip 4.4 MB -502 B (-0.0%) 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

@codeshaunted
codeshaunted enabled auto-merge July 21, 2026 00:55
@codeshaunted
codeshaunted added this pull request to the merge queue Jul 21, 2026
Merged via the queue into canary with commit 070b3a3 Jul 21, 2026
62 of 63 checks passed
@codeshaunted
codeshaunted deleted the avery/b-836 branch July 21, 2026 01:04
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