Skip to content

[codex] Fix inferred generic runtime type args#3702

Merged
aaronvg merged 14 commits into
canaryfrom
aaron/fix-interfaces-and-describe
Jun 5, 2026
Merged

[codex] Fix inferred generic runtime type args#3702
aaronvg merged 14 commits into
canaryfrom
aaron/fix-interfaces-and-describe

Conversation

@aaronvg

@aaronvg aaronvg commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Seed inferred generic call type arguments into runtime callee frames via CallPlan.type_args.
  • Preserve correct runtime generic frame layouts for static/unbound calls, bound receiver calls, and interface dispatch.
  • Port the related short-circuit slot-store fix and add the inferred generic speedtest workload.
  • Add runtime regressions for inferred free functions, inferred static constructors, iterator/default-method dispatch, nested rigid Self, and inferred method generics through interface dispatch.

Root Cause

TIR inferred generic call bindings, but inferred call-site type args were not consistently reaching the VM frame. Callees could run with unknown generic parameters, causing instances created by inferred static constructors to carry incorrect class_type_args, which then affected runtime IsType checks and interface dispatch.

Validation

  • target/debug/baml-cli test --from crates/baml_tests/baml_src -i '::free_function_sees_inferred_type_arg' -i '::inferred_static_ctor_instance_carries_class_type_args' -i '::inferred_ctor_default_method_collects_all' -i '::nested_self_inferred_binding_not_voided' -i '::interface_dispatch_sees_inferred_method_type_arg' -i '::showcase_lazy_cursor_pipeline'
  • INSTA_UPDATE=always cargo test -p baml_tests --test baml_src bytecode -- --nocapture
  • cargo test -p baml_tests --test baml_src baml_test -- --nocapture
  • git diff --check
  • uvx prek run --all-files

Note

Medium Risk
Touches codegen (short-circuit, type metadata), VM-facing return types, and project discovery; broad but covered by new e2e and describe snapshots.

Overview
Adds baml.iter to the stdlib: Iterable / Iterator with associated Item and Error, default methods (map, collect, etc.), adapter classes, and a blanket Iterable for T[].

CLI and docs: baml describe gains interface/associated-type rendering and a large keyword guide (implements, associated, projection, …). TS crosswalk no longer maps interface/extends/implements to “use composition”; new points at object-literal construction. Introspection treats a baml_src/ directory as a project marker (walk-up + tests).

Compiler / runtime surfacing: Function metadata now keeps separate display signatures (generics, T.Item, (Type as Interface).Assoc) via AssociatedProjectionResolver, wired into baml run --list and JSON listing so projections are not erased to void/unknown. Emit fixes short-circuit when the result lives in a real local (not stack-only), improves class IsType / field lookup by module path, and aligns comments with TypeVar → BuiltinUnknown erasure. E2E tests cover projected returns, list output, and manifest-less describe.

Reviewed by Cursor Bugbot for commit fc01d1e. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Added baml.iter standard library with Iterator/Iterable interfaces and adaptor types (Map, Filter, FlatMap, etc.).
    • Support for manifest-less projects via baml_src/ directory detection.
  • Improvements

    • Enhanced CLI describe and --list output with formatted type signatures and generic parameters.
    • Strengthened interface implementation validation and associated-type projection resolution.
    • Improved generic type-argument inference through interface dispatch and default methods.

@vercel

vercel Bot commented Jun 5, 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 Jun 5, 2026 11:06pm
promptfiddle Ready Ready Preview, Comment Jun 5, 2026 11:06pm
promptfiddle2 Ready Ready Preview, Comment Jun 5, 2026 11:06pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Wondering what really moved? Review this PR in Change Stack to inspect semantic changes, definitions, and references.

Review Change Stack

📝 Walkthrough

Walkthrough

Adds associated-type projection resolution and generic-bound-aware typing, refactors MIR dispatch and emit metadata, threads display signature info across VM/CLI, introduces iterator stdlib, updates LSP describe/diagnostics/rendering, enhances project discovery via baml_src, and expands extensive tests/snapshots.

Changes

Compiler and tooling integration

Layer / File(s) Summary
Keyword docs and CLI keyword tests
baml_language/crates/baml_builtins2/keyword_docs/*, baml_language/crates/baml_cli/src/describe_command_tests.rs
Expanded BAML/TS keyword docs; added/updated CLI keyword snapshot tests.
Project discovery and CLI E2E
baml_language/crates/baml_cli/src/project_load.rs, baml_language/crates/baml_cli/tests/exit_code_e2e.rs
Project root detection accepts baml_src; E2E covers describe/run/list on manifest-less projects and projections.
Runtime function display metadata, emit wiring, VM/type templates, and inferred-generic fixtures
baml_language/crates/baml_cli/src/run_command.rs, baml_language/crates/baml_compiler2_emit/*, baml_language/crates/bex_vm_types/*, baml_language/crates/bex_vm/*, baml_language/crates/bex_engine/*, baml_language/crates/baml_type/*, baml_language/crates/baml_exec/*, baml_language/crates/baml_tests/baml_src/ns_inferred_generic_type_args/*
Adds display signature fields end-to-end; updates IsType template matching and TyTemplate; includes inferred-generic tests and related docs.
TIR associated projection, generics/inference, interfaces, and type-expr lowering
baml_language/crates/baml_compiler2_tir/*
Introduces AssociatedProjectionResolver; refactors generics/inference/interface rule matching; extends substitutions and arity diagnostics.
MIR lowering and dispatch refinements
baml_language/crates/baml_compiler2_mir/src/lower.rs
Threads receiver typing through interface dispatch; updates type conversion/guards and class-receiver path handling.
LSP describe/type-info rendering and assoc-projection hovers
baml_language/crates/baml_lsp2_actions/src/{describe,type_info}*.rs, .../describe_tests.rs, .../test_files/on_hover/associated_type_projection_metadata.baml
Interfaces recognized as items; implements blocks rendered with content; describe/hover snapshots added.
LSP diagnostics snapshot updates and stdlib iterator hover inference
baml_language/crates/baml_lsp2_actions_tests/test_files/*
Reserved-const diagnostics formatting updated; iterator error fixtures; stdlib iterator hover inference expectations.
Formatter/trivia printing and idempotency test
baml_language/crates/baml_fmt/*
Refined printing/trivia handling; added idempotency regression test.
Iterator stdlib and comprehensive iterator tests
baml_language/crates/baml_builtins2/baml_std/baml/ns_iter/iter.baml, .../src/lib.rs, baml_language/crates/baml_tests/baml_src/ns_iter*/*
Adds baml.iter stdlib and registers it; broad iterator behavior tests across fixtures.
Generic-bound-aware implements and associated-type validation
baml_language/crates/baml_lsp2_actions/src/check.rs
Implements generic-bound-aware validation and signature matching for implements conformance.
Interfaces and associated-types test suites
baml_language/crates/baml_tests/tests/*, baml_language/crates/baml_tests/baml_src/ns_json_to_from_string/*, .../ns_exceptions/*
Extensive tests for implements, associated projections (compile/runtime/metadata), JSON generic inference, and exception callbacks.
SDK client codegen adjustments
baml_language/crates/baml_project/src/client_codegen.rs
Removes interface/default/implements methods from free functions; treats interface types as opaque in SDK; tests added.
Benchmark workload doc
baml_language/tools/speedtest/workloads/compute/generic-apply-inferred-1m.md
Adds 1M-iteration inferred-generic workload description.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Engine
  participant VM
  participant User
  CLI->>Engine: run --list (collect functions)
  Engine->>VM: fetch Functions (with display_* fields)
  VM-->>Engine: Function metadata (display types/params/return)
  Engine-->>CLI: JSON/debug list with display signatures
  User-->>CLI: reads generic params/types rendered
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • BoundaryML/baml#3676 — Also modifies CLI project discovery to treat baml_src as a valid project root with related tests.
  • BoundaryML/baml#3667 — Refactors interface dispatch and threads inferred type arguments through dispatch, overlapping MIR lowering here.
  • BoundaryML/baml#3652 — Advances associated-types handling in compiler2, aligning with this PR’s projection resolver and plumbing.

Poem

A rabbit taps its paw in codey cheer,
Projections clear, dispatch sincere;
Iterators hop, map, filter, find,
Types now show their gentler mind.
From baml_src we sniff the trail—
Describe and list shall never fail.
Thump-thump: metadata prevails! 🐇✨

✨ 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 aaron/fix-interfaces-and-describe

@aaronvg
aaronvg marked this pull request as ready for review June 5, 2026 00:34
@github-actions

github-actions Bot commented Jun 5, 2026

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.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

Binary size checks passed

7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
baml-cli Linux 🔒 18.4 MB 7.8 MB file 24.1 MB -5.7 MB (-23.5%) OK
packed-program Linux 🔒 13.8 MB 5.8 MB file 15.1 MB -1.3 MB (-8.6%) OK
baml-cli macOS 🔒 14.1 MB 6.8 MB file 18.2 MB -4.1 MB (-22.4%) OK
packed-program macOS 🔒 10.6 MB 5.0 MB file 11.5 MB -899.6 KB (-7.8%) OK
baml-cli Windows 🔒 15.5 MB 7.0 MB file 19.6 MB -4.2 MB (-21.2%) OK
packed-program Windows 🔒 11.5 MB 5.2 MB file 12.2 MB -748.2 KB (-6.1%) OK
bridge_wasm WASM 12.5 MB 🔒 3.5 MB gzip 3.9 MB -357.1 KB (-9.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

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
baml_language/crates/baml_cli/src/run_command.rs (1)

1386-1397: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid lossy zip when rendering function params.

Using param_names.zip(display_param_types) silently drops trailing params if those vectors ever diverge, yielding incomplete --list output (both debug and JSON). Iterate by index and fallback for missing display types.

Proposed fix
-                let params: Vec<String> = func
-                    .param_names
-                    .iter()
-                    .zip(func.display_param_types.iter())
-                    .enumerate()
-                    .map(|(idx, (name, ty))| {
+                let params: Vec<String> = func
+                    .param_names
+                    .iter()
+                    .enumerate()
+                    .map(|(idx, name)| {
+                        let ty = func
+                            .display_param_types
+                            .get(idx)
+                            .map(String::as_str)
+                            .unwrap_or("unknown");
                         let opt = if func.param_has_default.get(idx).copied().unwrap_or(false) {
                             " [optional]"
                         } else {
                             ""
                         };
                         format!("{name}: {ty}{opt}")
                     })
                     .collect();
-                let params: Vec<serde_json::Value> = f
-                    .param_names
-                    .iter()
-                    .zip(f.display_param_types.iter())
-                    .map(|(name, ty)| {
+                let params: Vec<serde_json::Value> = f
+                    .param_names
+                    .iter()
+                    .enumerate()
+                    .map(|(idx, name)| {
+                        let ty = f
+                            .display_param_types
+                            .get(idx)
+                            .map(String::as_str)
+                            .unwrap_or("unknown");
                         serde_json::json!({
                             "name": name,
                             "type": ty,
                         })
                     })
                     .collect();

Also applies to: 1461-1468

🤖 Prompt for 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.

In `@baml_language/crates/baml_cli/src/run_command.rs` around lines 1386 - 1397,
The zip silently drops parameters when param_names and func.display_param_types
differ; change the map to iterate over indices (0..param_names.len()) and for
each idx fetch name = param_names[idx], ty =
func.display_param_types.get(idx).cloned().unwrap_or_else(|| "<unknown>".into())
and keep the optional suffix logic using
func.param_has_default.get(idx).copied().unwrap_or(false); apply the same
indexed iteration fix to the similar block that uses param_names with
display_param_types (the one around the second occurrence mentioned).
baml_language/crates/baml_lsp2_actions/src/check.rs (1)

3863-3885: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Expand alias targets before collecting implicit class methods.

This helper only proceeds when lower_type_expr_in_ns() returns Ty::Class directly. validate_implements_for() already accepts alias for targets by normalizing them through the alias-expansion path, so implements I for UserAlias {} can now skip the underlying class methods here and fall through to false missing/signature-mismatch diagnostics.

🤖 Prompt for 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.

In `@baml_language/crates/baml_lsp2_actions/src/check.rs` around lines 3863 -
3885, The current block only handles a direct Ty::Class from
lower_type_expr_in_ns (target_ty) and bails out on aliases; update
validate_implements_for (or the surrounding helper) to resolve/expand alias
targets produced by lower_type_expr_in_ns before collecting implicit class
methods: if target_ty is an alias/indirection, follow the
alias-expansion/normalization path used elsewhere (the same logic you use for
implements I for UserAlias) to obtain the underlying Ty::Class, populate
target_diags accordingly, then continue using
class_qtn/class_loc/item_tree/class_data as before so alias-backed targets are
handled instead of returning early.
baml_language/crates/baml_compiler2_mir/src/lower.rs (1)

8364-8369: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the active generic bounds when comparing projected TIR types.

interface_tir_assoc_match() now relies on tir_types_equivalent(), but tir_types_equivalent() builds AssociatedProjectionResolver with &(). That means projections like T::Item only normalize in the dispatch-target helpers, not in this assoc-binding filter, so a valid candidate can still be dropped here as “not equivalent”.

Suggested fix
     fn tir_types_equivalent(&self, a: &Tir2Ty, b: &Tir2Ty) -> bool {
         let resolver = baml_compiler2_tir::associated_projection::AssociatedProjectionResolver::new(
             self.db,
             &self.resolved_aliases.aliases,
-            &(),
+            &self.generic_param_bounds,
         );
         let resolved_a = resolver.resolve_deep(a);
         let resolved_b = resolver.resolve_deep(b);
         resolver.types_equivalent(&resolved_a, &resolved_b)
     }

Also applies to: 8415-8424

🤖 Prompt for 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.

In `@baml_language/crates/baml_compiler2_mir/src/lower.rs` around lines 8364 -
8369, The assoc-binding filter in interface_tir_assoc_match() uses
tir_types_equivalent(), but tir_types_equivalent() currently constructs an
AssociatedProjectionResolver with &(), so projections (e.g. T::Item) aren’t
normalized under the active generic bounds and valid candidates get rejected;
fix this by making tir_types_equivalent accept or construct an
AssociatedProjectionResolver built from the current active generic bounds (the
generic environment used by interface_tir_assoc_match) instead of &(), and
update the two callsites (the block using iface_assoc/impl_iface_assoc around
the shown diff and the other occurrence at the 8415-8424 region) to pass the
resolver (or the generic-bounds/context) so projected TIR types normalize when
comparing.
🧹 Nitpick comments (2)
baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs (1)

1175-1193: ⚡ Quick win

Add a unit test for the preserving-Self.Assoc path.

The added tests only cover the eager projection rewrite in substitute_self_in. The new public helper's defining behavior is the opposite one—keeping Self.Record as a plain TypeExpr::Path—and that regression isn't guarded yet. A small unit test here would lock down the behavior this helper was added for.

As per coding guidelines, **/*.rs: Prefer writing Rust unit tests over integration tests where possible.

🤖 Prompt for 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.

In `@baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs` around lines
1175 - 1193, Add a unit test that ensures the new public helper preserves a
`Self.Record` path instead of rewriting it to an associated projection:
construct the input with `path_segments(&["Self", "Record"])` and a suitable
`replacement` (e.g. `path("UserRepository")`), call the new helper (the
preserve-`Self.Assoc` helper) and assert the result is a `TypeExpr::Path` equal
to the original path (or contains the expected segments) and not a
`TypeExpr::AssociatedTypeProjection`; reuse the patterns from the existing
`substitute_self_rewrites_member_path_to_projection` test (`path_segments`,
`path`, and `TypeExpr` pattern checks) to locate and add this test next to it.

Source: Coding guidelines

baml_language/crates/baml_compiler2_tir/src/builder.rs (1)

1965-2090: ⚡ Quick win

Add local unit coverage for the frame-layout helpers.

runtime_call_type_args(), callee_frame_generic_params(), and runtime_type_arg_params_for_call() encode the exact ordering contract this PR is fixing. A small table-driven #[cfg(test)] block here would pin the bound/unbound/static/interface cases without relying only on end-to-end regressions. As per coding guidelines, prefer writing Rust unit tests over integration tests where possible.

🤖 Prompt for 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.

In `@baml_language/crates/baml_compiler2_tir/src/builder.rs` around lines 1965 -
2090, Add a local #[cfg(test)] mod in this file with table-driven unit tests
that exercise runtime_call_type_args, callee_frame_generic_params, and
runtime_type_arg_params_for_call across the key scenarios (free function, bound
method, unbound method, interface default method with is_method_call true/false,
field/variant, and value-call). For each case construct minimal inputs (ExprId,
Name slices for callee_generic_params, fake MemberResolution values or build the
minimal context so callee_member_resolution returns the intended resolution) and
assert the returned Vec<Name>/Vec<Ty> ordering matches the expected
owner-then-fn or fn-only sequences; include tests that exercise
runtime_call_type_args with bindings that both do and do not resolve associated
projections and with typevars outside the current generic_params to hit the
BuiltinUnknown fallback. Ensure tests live in the same file so they validate the
helper ordering contract without relying on integration tests.

Source: Coding guidelines

🤖 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_mir/src/lower.rs`:
- Around line 5508-5510: The code disables CallPlan.type_args when is_sys_op is
true (via calling lower_call_type_args(expr_id, !is_sys_op)), which drops
inferred type arguments for builtin IO/sys-op functions that still expect
synthetic trailing runtime type operands; update the logic in lower.rs so that
lower_call_type_args is called with a flag that preserves type-arg operands for
sys-ops that reserve synthetic trailing args instead of unconditionally passing
!is_sys_op—specifically, change the condition around
check_sys_op/call_type_arg_operands (and the analogous sites around the other
occurrences) to detect whether the callee is a sys-op that reserves synthetic
trailing type operands and only suppress type_args when it truly has no runtime
type operands, ensuring CallPlan.type_args remains populated for IO builtins and
inferred generics (use the existing check_sys_op and expr_id to decide this).

In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 2011-2025: callee_frame_generic_params() is dropping method
generic params for impl-block and non-default interface methods; update the
branches so the returned tuple always includes the function's own generic_params
as the second element and resolve interface ownership by checking interfaces for
both default and regular methods. Concretely: when matching
item_tree.implements_for (the impl variable `imp`) return
(imp.generic_params.clone(), fn_params) instead of an empty Vec, and when
locating an owning interface use interfaces.values().find(|iface|
iface.default_methods.contains(&func_id) || iface.methods.contains(&func_id))
and return (iface.generic_params.clone(), fn_params). Apply the same change in
the duplicate block around the other occurrence (lines ~2062-2071) so
runtime_type_arg_params_for_call()/CallPlan.type_args see the full set of
generic parameters.

In `@baml_language/crates/baml_compiler2_tir/src/interfaces.rs`:
- Around line 330-346: The code currently applies generics::substitute_ty to
requested_iface_ty using the rule's bindings which can capture caller-side type
variables with the same Name; instead, compare the instantiated rule interface
(instantiated_interface_ty from generics::substitute_ty(&rule.interface_ty,
&bindings)) directly against the original requested_iface_ty (do NOT call
generics::substitute_ty on requested_iface_ty) when calling
interface_ty_satisfies_request and when constructing
InterfaceImplInstantiation.for_ty; update the three other similar branches (the
ones around the other occurrences mentioned) the same way, and add a regression
test that reproduces the name-collision case (e.g., rule implements<T>
Iterable<T> for Box<T> versus caller request Iterable<T> with a different T) to
ensure caller type vars are not captured.

In `@baml_language/crates/baml_lsp2_actions/src/check.rs`:
- Around line 4642-4676: The implicit-interface check is failing when only
parameter names differ because MethodSignature::matches is being used on
source.signature with a SignatureMatchContext that enforces exact parameter-name
equality; update the check so parameter-name spelling is ignored for these
implicit class-method matches — either add a flag to SignatureMatchContext
(e.g., ignore_param_names) and pass it when calling MethodSignature::matches
from the loop over target_class_methods/provided_method_names, or add/use a
variant of MethodSignature::matches that ignores parameter names; ensure the
same change is applied to the other analogous block around lines 5030-5063 that
also calls MethodSignature::matches on implicitly provided methods.
- Around line 1549-1593: The remaining validators
validate_associated_type_bindings_on_interface_type and
validate_associated_type_binding_defs still call ty_nominal_subtype (so they
ignore generic bounds); update both call sites to call
ty_nominal_subtype_with_generic_bounds instead, forwarding the same db, sub, sup
plus pkg_items, namespace_path, generic_params, generic_bounds and aliases
arguments currently available in those functions so bound-aware TypeVar checks
are used; ensure the parameter order matches
ty_nominal_subtype_with_generic_bounds(signature) and adjust any local variable
names to supply the correct namespace_path/generic_* arguments.

In
`@baml_language/tools/speedtest/workloads/compute/generic-apply-inferred-1m.md`:
- Line 29: Update the section heading that currently reads "## Typescript" to
use the canonical casing "## TypeScript" so it matches standard
language/documentation conventions; locate the heading in the markdown file (the
string "## Typescript") and replace it with "## TypeScript".

---

Outside diff comments:
In `@baml_language/crates/baml_cli/src/run_command.rs`:
- Around line 1386-1397: The zip silently drops parameters when param_names and
func.display_param_types differ; change the map to iterate over indices
(0..param_names.len()) and for each idx fetch name = param_names[idx], ty =
func.display_param_types.get(idx).cloned().unwrap_or_else(|| "<unknown>".into())
and keep the optional suffix logic using
func.param_has_default.get(idx).copied().unwrap_or(false); apply the same
indexed iteration fix to the similar block that uses param_names with
display_param_types (the one around the second occurrence mentioned).

In `@baml_language/crates/baml_compiler2_mir/src/lower.rs`:
- Around line 8364-8369: The assoc-binding filter in interface_tir_assoc_match()
uses tir_types_equivalent(), but tir_types_equivalent() currently constructs an
AssociatedProjectionResolver with &(), so projections (e.g. T::Item) aren’t
normalized under the active generic bounds and valid candidates get rejected;
fix this by making tir_types_equivalent accept or construct an
AssociatedProjectionResolver built from the current active generic bounds (the
generic environment used by interface_tir_assoc_match) instead of &(), and
update the two callsites (the block using iface_assoc/impl_iface_assoc around
the shown diff and the other occurrence at the 8415-8424 region) to pass the
resolver (or the generic-bounds/context) so projected TIR types normalize when
comparing.

In `@baml_language/crates/baml_lsp2_actions/src/check.rs`:
- Around line 3863-3885: The current block only handles a direct Ty::Class from
lower_type_expr_in_ns (target_ty) and bails out on aliases; update
validate_implements_for (or the surrounding helper) to resolve/expand alias
targets produced by lower_type_expr_in_ns before collecting implicit class
methods: if target_ty is an alias/indirection, follow the
alias-expansion/normalization path used elsewhere (the same logic you use for
implements I for UserAlias) to obtain the underlying Ty::Class, populate
target_diags accordingly, then continue using
class_qtn/class_loc/item_tree/class_data as before so alias-backed targets are
handled instead of returning early.

---

Nitpick comments:
In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 1965-2090: Add a local #[cfg(test)] mod in this file with
table-driven unit tests that exercise runtime_call_type_args,
callee_frame_generic_params, and runtime_type_arg_params_for_call across the key
scenarios (free function, bound method, unbound method, interface default method
with is_method_call true/false, field/variant, and value-call). For each case
construct minimal inputs (ExprId, Name slices for callee_generic_params, fake
MemberResolution values or build the minimal context so callee_member_resolution
returns the intended resolution) and assert the returned Vec<Name>/Vec<Ty>
ordering matches the expected owner-then-fn or fn-only sequences; include tests
that exercise runtime_call_type_args with bindings that both do and do not
resolve associated projections and with typevars outside the current
generic_params to hit the BuiltinUnknown fallback. Ensure tests live in the same
file so they validate the helper ordering contract without relying on
integration tests.

In `@baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs`:
- Around line 1175-1193: Add a unit test that ensures the new public helper
preserves a `Self.Record` path instead of rewriting it to an associated
projection: construct the input with `path_segments(&["Self", "Record"])` and a
suitable `replacement` (e.g. `path("UserRepository")`), call the new helper (the
preserve-`Self.Assoc` helper) and assert the result is a `TypeExpr::Path` equal
to the original path (or contains the expected segments) and not a
`TypeExpr::AssociatedTypeProjection`; reuse the patterns from the existing
`substitute_self_rewrites_member_path_to_projection` test (`path_segments`,
`path`, and `TypeExpr` pattern checks) to locate and add this test next to it.
🪄 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: 12049bec-07b3-4f0f-9ab7-0576bf3852de

📥 Commits

Reviewing files that changed from the base of the PR and between 654e9c1 and 27fcef9.

⛔ Files ignored due to path filters (42)
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__dispatch_keyword_class.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__dispatch_keyword_interfaces.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_class_shows_associated_type_bindings.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_interface.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_as.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_associated.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_blanket.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_bounds.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_check.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_class.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_extends.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_field.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_generic.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_generics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_impl.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_implements.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_interface.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_interfaces.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_method.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_projection.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_requires.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_self.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_ts_interface.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_ts_new.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_type.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_types.snap is excluded by !**/*.snap
  • baml_language/crates/baml_lsp2_actions/src/snapshots/baml_lsp2_actions__describe_tests__describe_interface.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/bigints.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/builtins.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/class_type_args_at_runtime.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/closures.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/deep_copy.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/fs.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/instantiation_expr.qualified.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/instantiation_expr.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/json_auto_derive.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/json_to_from_string.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/lambdas.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/reflect_type_of_generic.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/type_error_repro.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/type_reflection.snap is excluded by !**/*.snap
📒 Files selected for processing (40)
  • baml_language/crates/baml_builtins2/keyword_docs/baml_keywords.yaml
  • baml_language/crates/baml_builtins2/keyword_docs/ts_keywords.yaml
  • baml_language/crates/baml_cli/src/describe_command_tests.rs
  • baml_language/crates/baml_cli/src/project_load.rs
  • baml_language/crates/baml_cli/src/run_command.rs
  • baml_language/crates/baml_cli/tests/exit_code_e2e.rs
  • baml_language/crates/baml_compiler2_ast/src/auto_derive_json.rs
  • baml_language/crates/baml_compiler2_emit/src/emit.rs
  • baml_language/crates/baml_compiler2_emit/src/lib.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/src/associated_projection.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/inference.rs
  • baml_language/crates/baml_compiler2_tir/src/interfaces.rs
  • baml_language/crates/baml_compiler2_tir/src/lib.rs
  • baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs
  • baml_language/crates/baml_exec/src/clap_target.rs
  • baml_language/crates/baml_fmt/src/ast/declarations.rs
  • baml_language/crates/baml_fmt/src/ast/mod.rs
  • baml_language/crates/baml_fmt/src/lib.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_lsp2_actions/src/describe.rs
  • baml_language/crates/baml_lsp2_actions/src/describe_tests.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info_tests.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/associated_type_projection_metadata.baml
  • baml_language/crates/baml_tests/baml_src/ns_inferred_generic_type_args/inferred_generic_type_args.baml
  • baml_language/crates/baml_tests/tests/interfaces.rs
  • baml_language/crates/baml_tests/tests/interfaces_associated_types.rs
  • baml_language/crates/baml_tests/tests/interfaces_class_generics.rs
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_vm/src/package_baml/json.rs
  • baml_language/crates/bex_vm/src/package_baml/mod.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/bex_vm/tests/load_type.rs
  • baml_language/crates/bex_vm/tests/method_class_type_args.rs
  • baml_language/crates/bex_vm_types/src/types.rs
  • baml_language/sdk_tests/fixtures/type_shapes/baml_src/ns_generics/methods.baml
  • baml_language/tools/speedtest/workloads/compute/generic-apply-inferred-1m.md

Comment thread baml_language/crates/baml_compiler2_mir/src/lower.rs Outdated
Comment thread baml_language/crates/baml_compiler2_tir/src/builder.rs
Comment thread baml_language/crates/baml_compiler2_tir/src/interfaces.rs
Comment thread baml_language/crates/baml_lsp2_actions/src/check.rs
Comment thread baml_language/crates/baml_lsp2_actions/src/check.rs
Comment thread baml_language/tools/speedtest/workloads/compute/generic-apply-inferred-1m.md Outdated

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
baml_language/crates/baml_lsp2_actions/src/check.rs (1)

2240-2266: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use generic_bounds when validating unqualified T.Item projections.

Line 2451 threads generic_bounds into validate_unqualified_associated_type_projection(), but that helper still bails out on Ty::TypeVar. The only typevar-specific projection check lives in validate_ambiguous_typevar_associated_projection_in_type_expr(), and that is only called from function/method signature walkers. As a result, non-signature type expressions like class/interface fields or associated-type bounds/defaults can still miss the new unknown/ambiguous diagnostics for T.Item.

Suggested fix
 fn validate_unqualified_associated_type_projection(
     db: &dyn Db,
     file_id: FileId,
     expr: &baml_compiler2_ast::TypeExpr,
     segments: &[Name],
     span: TextRange,
     pkg_items: &baml_compiler2_hir::package::PackageItems<'_>,
     namespace_path: &[Name],
     generic_params: &[Name],
-    _generic_bounds: &GenericBoundExprMap,
+    generic_bounds: &GenericBoundExprMap,
     aliases: &std::collections::HashMap<QualifiedTypeName, Ty>,
     diagnostics: &mut Vec<Diagnostic>,
 ) {
@@
     let expanded_base_ty = expand_alias_chain(base_ty, aliases);
     let message = match expanded_base_ty {
+        Ty::TypeVar(typevar, _) => {
+            let Some(bound) = generic_bounds.get(&typevar) else {
+                return;
+            };
+            let sources = associated_type_projection_sources_for_interface_bound(
+                db,
+                bound,
+                member,
+                pkg_items,
+                namespace_path,
+            );
+            if sources.len() == 1 {
+                return;
+            }
+            if sources.is_empty() {
+                format!("unknown associated type `{member}` for bound `{bound}`")
+            } else {
+                let base = base_expr.to_string();
+                let alternatives = sources
+                    .iter()
+                    .map(|interface| format!("({base} as {interface}).{member}"))
+                    .collect::<Vec<_>>()
+                    .join(", ");
+                format!(
+                    "ambiguous associated type projection `{expr}`; disambiguate with one of: {alternatives}"
+                )
+            }
+        }
         Ty::Class(class_qtn, _, _) => {
             let matches = matching_associated_type_projection_interfaces(db, &class_qtn, member)
                 .unwrap_or_default();

Also applies to: 2451-2452

🤖 Prompt for 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.

In `@baml_language/crates/baml_lsp2_actions/src/check.rs` around lines 2240 -
2266, The helper validate_unqualified_associated_type_projection currently bails
for Ty::TypeVar and thus misses unknown/ambiguous diagnostics for non-signature
type expressions; update validate_unqualified_associated_type_projection (the
call site at the shown diff) to consult the passed generic_bounds and, when
encountering a Ty::TypeVar, invoke the same logic used for signature walkers by
calling validate_ambiguous_typevar_associated_projection_in_type_expr (or
inlining its checks) so typevar-associated projections produce the
unknown/ambiguous diagnostics; ensure the function no longer early-returns on
Ty::TypeVar and uses generic_bounds to disambiguate projections.
🤖 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.

Outside diff comments:
In `@baml_language/crates/baml_lsp2_actions/src/check.rs`:
- Around line 2240-2266: The helper
validate_unqualified_associated_type_projection currently bails for Ty::TypeVar
and thus misses unknown/ambiguous diagnostics for non-signature type
expressions; update validate_unqualified_associated_type_projection (the call
site at the shown diff) to consult the passed generic_bounds and, when
encountering a Ty::TypeVar, invoke the same logic used for signature walkers by
calling validate_ambiguous_typevar_associated_projection_in_type_expr (or
inlining its checks) so typevar-associated projections produce the
unknown/ambiguous diagnostics; ensure the function no longer early-returns on
Ty::TypeVar and uses generic_bounds to disambiguate projections.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5d687092-5878-4382-8559-1fbd3db1f57e

📥 Commits

Reviewing files that changed from the base of the PR and between 917578d and f7d0508.

⛔ Files ignored due to path filters (4)
  • baml_language/crates/baml_tests/snapshots/baml_src/iter.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/json_to_from_string.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap is excluded by !**/*.snap
📒 Files selected for processing (7)
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_tests/baml_src/ns_json_to_from_string/json_to_from_string.baml
  • baml_language/crates/baml_tests/tests/interfaces.rs
  • baml_language/crates/baml_tests/tests/interfaces_associated_types.rs
  • baml_language/tools/speedtest/workloads/compute/generic-apply-inferred-1m.md
✅ Files skipped from review due to trivial changes (1)
  • baml_language/tools/speedtest/workloads/compute/generic-apply-inferred-1m.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • baml_language/crates/baml_tests/tests/interfaces.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f7d0508. Configure here.

Comment thread baml_language/crates/baml_compiler2_mir/src/lower.rs Outdated
Comment thread baml_language/crates/baml_compiler2_mir/src/lower.rs

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
baml_language/crates/baml_compiler2_mir/src/lower.rs (2)

5656-5765: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep IO builtin arity aligned with the new synthetic-count helper.

This helper now subtracts declared type value params, but lower_function() still adds one extra IO-builtin arity slot per function generic param at Line 12483. That makes caller and callee disagree on how many trailing runtime type operands exist, so a builtin like fn f<T, U>(ty: type, ...) will shift its argument frame and read the wrong operand positions.

Suggested fix
-            let extra_arity = if matches!(kind, BuiltinKind::Io) {
-                // For IO builtins (`$rust_io_function`), the compiler injects
-                // one synthetic trailing value-arg slot for each *function-level*
-                // generic type parameter.  Class-level generics (from the
-                // enclosing class definition) do NOT generate extra slots —
-                // `baml_builtins2_codegen` only adds type-arg params for
-                // function-level generics.  We therefore only count the
-                // function's own generic_params here.
-                let item_tree = file_item_tree(db, func_loc.file(db));
-                item_tree[func_loc.id(db)].generic_params.len()
-            } else {
-                0
-            };
+            let extra_arity = if matches!(kind, BuiltinKind::Io) {
+                let item_tree = file_item_tree(db, func_loc.file(db));
+                let func = &item_tree[func_loc.id(db)];
+                let declared_type_value_params = func
+                    .params
+                    .iter()
+                    .filter(|param| {
+                        matches!(
+                            param.type_expr.as_ref().map(|ty| &ty.expr),
+                            Some(baml_compiler2_ast::TypeExpr::Type { .. })
+                        )
+                    })
+                    .count();
+                func.generic_params
+                    .len()
+                    .saturating_sub(declared_type_value_params)
+            } else {
+                0
+            };
🤖 Prompt for 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.

In `@baml_language/crates/baml_compiler2_mir/src/lower.rs` around lines 5656 -
5765, The IO-builtin arity calculation is now moved into
synthetic_type_arg_count_for_sys_op (which subtracts declared `type` value
params) but lower_function still unconditionally adds one extra IO builtin arity
slot per generic param, causing caller/callee arity mismatch; update
lower_function to compute IO builtin synthetic type-arg count by calling
sys_op_synthetic_type_arg_count (or replicate its logic: use
synthetic_type_arg_count_for_sys_op for the resolved FunctionLoc) instead of
adding generic param count directly so the added arity matches the new helper
and declared `type` params are excluded.

6074-6124: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't elide capped sys-op type args when they're BuiltinUnknown.

When max_count is Some(k), the caller is explicitly requesting k positional runtime type operands. The fast-path at Lines 6118-6123 returns an empty vec if every inferred arg was rewritten to BuiltinUnknown, which collapses those synthetic sys-op operands back to zero and reintroduces the wrong-arity call shape for unresolved generic calls.

Suggested fix
-        if inferred_type_args
-            .iter()
-            .all(|ty| matches!(ty, Tir2Ty::BuiltinUnknown { .. } | Tir2Ty::Unknown { .. }))
-        {
-            return Vec::new();
-        }
+        if max_count.is_none()
+            && inferred_type_args
+                .iter()
+                .all(|ty| matches!(ty, Tir2Ty::BuiltinUnknown { .. } | Tir2Ty::Unknown { .. }))
+        {
+            return Vec::new();
+        }
🤖 Prompt for 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.

In `@baml_language/crates/baml_compiler2_mir/src/lower.rs` around lines 6074 -
6124, The function lower_call_type_args currently returns an empty Vec when all
inferred_type_args are Unknown, which incorrectly collapses sys-op type operands
when max_count is Some(k); update lower_call_type_args so that the "all unknown
-> return Vec::new()" early-return is suppressed when a max_count was requested.
Concretely, in lower_call_type_args (use symbols: inferred_type_args, max_count,
emit_frame_type_arg_ops, lower_call_type_args) remove or guard the check that
returns Vec::new() for all-BuiltinUnknown/Unknown cases so that when
max_count.is_some() you still call emit_frame_type_arg_ops on the (possibly
truncated) inferred_type_args to emit the k positional type operands. Ensure
behavior for max_count == None remains unchanged.
🤖 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.

Outside diff comments:
In `@baml_language/crates/baml_compiler2_mir/src/lower.rs`:
- Around line 5656-5765: The IO-builtin arity calculation is now moved into
synthetic_type_arg_count_for_sys_op (which subtracts declared `type` value
params) but lower_function still unconditionally adds one extra IO builtin arity
slot per generic param, causing caller/callee arity mismatch; update
lower_function to compute IO builtin synthetic type-arg count by calling
sys_op_synthetic_type_arg_count (or replicate its logic: use
synthetic_type_arg_count_for_sys_op for the resolved FunctionLoc) instead of
adding generic param count directly so the added arity matches the new helper
and declared `type` params are excluded.
- Around line 6074-6124: The function lower_call_type_args currently returns an
empty Vec when all inferred_type_args are Unknown, which incorrectly collapses
sys-op type operands when max_count is Some(k); update lower_call_type_args so
that the "all unknown -> return Vec::new()" early-return is suppressed when a
max_count was requested. Concretely, in lower_call_type_args (use symbols:
inferred_type_args, max_count, emit_frame_type_arg_ops, lower_call_type_args)
remove or guard the check that returns Vec::new() for all-BuiltinUnknown/Unknown
cases so that when max_count.is_some() you still call emit_frame_type_arg_ops on
the (possibly truncated) inferred_type_args to emit the k positional type
operands. Ensure behavior for max_count == None remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 46593c1d-8498-4214-8c78-f867aaac73f4

📥 Commits

Reviewing files that changed from the base of the PR and between f7d0508 and 38a7826.

⛔ Files ignored due to path filters (2)
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap is excluded by !**/*.snap
📒 Files selected for processing (1)
  • baml_language/crates/baml_compiler2_mir/src/lower.rs

@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: 3

🤖 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_builtins2/baml_std/baml/ns_iter/iter.baml`:
- Around line 143-149: The next(self) iterator uses self.arr.at(self.idx) and
treats null as Done which prematurely ends iteration for nullable element types;
change next to first check if self.idx >= self.arr.length() and return Done in
that case, otherwise read item = self.arr.at(self.idx), increment self.idx, and
return item (allowing the item to be null) so that in-bounds nulls are yielded
rather than terminating iteration (refer to function next, self.idx, self.arr.at
and arr.length()).
- Around line 160-162: Range currently allows step==0 and treats exhaustion only
as self.i < self.max, causing infinite loops and incorrect behavior for negative
steps; update Range.new to validate and throw for step == 0 (make new() return
throws on invalid args) and update next() to handle sign-aware exhaustion and
advancement: when step > 0 use self.i < self.max and increment by step, when
step < 0 use self.i > self.max and add step (negative) to i; reference the Range
struct fields i, max, step and the methods new() and next() so the checks reject
zero-steps and correctly support negative steps.

In `@baml_language/crates/baml_compiler2_tir/src/generics.rs`:
- Around line 229-237: The union lowering needs to normalize members after
lowering so Optional -> Ty::nullable doesn't produce nested Union nodes; in the
TypeExpr::Union arm (and similarly in the other union-handling block around the
268-285 region) collect lowered members, iterate them and flatten any Ty::Union
members into the top-level member list, then deduplicate/null-normalize the list
(so nullable_non_null_part can detect a top-level null); finally construct the
resulting Ty::Union (or call the existing union construction helper) from the
flattened/deduped members instead of directly wrapping possibly-nested unions.
🪄 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: b3bebdfe-9543-42d3-899c-792120cb5ee7

📥 Commits

Reviewing files that changed from the base of the PR and between f7d0508 and 6ab4d6c.

⛔ Files ignored due to path filters (33)
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/_root.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/exceptions.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/iter.core.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/iter.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.core.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/type_reflection.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__testing_std__/baml_tests__compiles____testing_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__testing_std__/baml_tests__compiles____testing_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/comment_in_type/baml_tests__compiles__comment_in_type__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/json_llm_return_type/baml_tests__compiles__json_llm_return_type__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/optional_function_parameters/baml_tests__compiles__optional_function_parameters__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/optional_function_parameters/baml_tests__compiles__optional_function_parameters__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/stream_llm_inferred_typeargs/baml_tests__compiles__stream_llm_inferred_typeargs__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_vibes_nested/baml_tests__compiles__testset_vibes_nested__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/type_builder_errors/baml_tests__compiles__type_builder_errors__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/type_builder_test/baml_tests__compiles__type_builder_test__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/generics/baml_tests__diagnostic_errors__generics__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/patterns_class_destructure_namespaces/baml_tests__diagnostic_errors__patterns_class_destructure_namespaces__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_textual.snap is excluded by !**/*.snap
📒 Files selected for processing (37)
  • baml_language/crates/baml_builtins2/baml_std/baml/ns_iter/iter.baml
  • baml_language/crates/baml_builtins2/src/lib.rs
  • baml_language/crates/baml_cli/tests/exit_code_e2e.rs
  • baml_language/crates/baml_compiler2_emit/src/emit.rs
  • baml_language/crates/baml_compiler2_emit/src/lib.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/src/associated_projection.rs
  • 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/inference.rs
  • baml_language/crates/baml_compiler2_tir/src/interfaces.rs
  • baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs
  • baml_language/crates/baml_fmt/src/lib.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_lsp2_actions/src/describe.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info_tests.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/iterator_generics_inference.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/stdlib_iterator_inference.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/const_binding.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_binding.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_reserved_binding_names.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/iterator/stdlib_iterator_errors.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/loops/const_for_loops.baml
  • baml_language/crates/baml_project/src/client_codegen.rs
  • baml_language/crates/baml_tests/baml_src/ns_exceptions/exceptions.baml
  • baml_language/crates/baml_tests/baml_src/ns_iter/iter.baml
  • baml_language/crates/baml_tests/baml_src/ns_iter_impl_generics_only/iter.baml
  • baml_language/crates/baml_tests/baml_src/ns_iter_impl_generics_only/ns_core/core.baml
  • baml_language/crates/baml_tests/tests/interfaces_associated_types.rs
  • baml_language/crates/baml_type/src/template.rs
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_vm/src/package_baml/json.rs
  • baml_language/crates/bex_vm/src/package_baml/mod.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/bex_vm_types/src/types.rs
💤 Files with no reviewable changes (9)
  • baml_language/crates/bex_vm/src/package_baml/mod.rs
  • baml_language/crates/baml_type/src/template.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info.rs
  • baml_language/crates/bex_vm_types/src/types.rs
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_vm/src/package_baml/json.rs
  • baml_language/crates/baml_tests/tests/interfaces_associated_types.rs
✅ Files skipped from review due to trivial changes (4)
  • baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/const_binding.baml
  • baml_language/crates/baml_builtins2/src/lib.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/loops/const_for_loops.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_binding.baml
🚧 Files skipped from review as they are similar to previous changes (12)
  • baml_language/crates/baml_fmt/src/lib.rs
  • baml_language/crates/baml_lsp2_actions/src/describe.rs
  • baml_language/crates/baml_compiler2_emit/src/emit.rs
  • baml_language/crates/baml_tests/baml_src/ns_iter/iter.baml
  • baml_language/crates/baml_cli/tests/exit_code_e2e.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info_tests.rs
  • baml_language/crates/baml_compiler2_tir/src/interfaces.rs
  • baml_language/crates/baml_compiler2_emit/src/lib.rs
  • baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/iterator_generics_inference.baml
  • baml_language/crates/baml_compiler2_tir/src/associated_projection.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🤖 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_builtins2/baml_std/baml/ns_iter/iter.baml`:
- Around line 143-149: The next(self) iterator uses self.arr.at(self.idx) and
treats null as Done which prematurely ends iteration for nullable element types;
change next to first check if self.idx >= self.arr.length() and return Done in
that case, otherwise read item = self.arr.at(self.idx), increment self.idx, and
return item (allowing the item to be null) so that in-bounds nulls are yielded
rather than terminating iteration (refer to function next, self.idx, self.arr.at
and arr.length()).
- Around line 160-162: Range currently allows step==0 and treats exhaustion only
as self.i < self.max, causing infinite loops and incorrect behavior for negative
steps; update Range.new to validate and throw for step == 0 (make new() return
throws on invalid args) and update next() to handle sign-aware exhaustion and
advancement: when step > 0 use self.i < self.max and increment by step, when
step < 0 use self.i > self.max and add step (negative) to i; reference the Range
struct fields i, max, step and the methods new() and next() so the checks reject
zero-steps and correctly support negative steps.

In `@baml_language/crates/baml_compiler2_tir/src/generics.rs`:
- Around line 229-237: The union lowering needs to normalize members after
lowering so Optional -> Ty::nullable doesn't produce nested Union nodes; in the
TypeExpr::Union arm (and similarly in the other union-handling block around the
268-285 region) collect lowered members, iterate them and flatten any Ty::Union
members into the top-level member list, then deduplicate/null-normalize the list
(so nullable_non_null_part can detect a top-level null); finally construct the
resulting Ty::Union (or call the existing union construction helper) from the
flattened/deduped members instead of directly wrapping possibly-nested unions.
🪄 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: b3bebdfe-9543-42d3-899c-792120cb5ee7

📥 Commits

Reviewing files that changed from the base of the PR and between f7d0508 and 6ab4d6c.

⛔ Files ignored due to path filters (33)
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/_root.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/exceptions.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/iter.core.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/iter.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.core.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/type_reflection.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__testing_std__/baml_tests__compiles____testing_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__testing_std__/baml_tests__compiles____testing_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/comment_in_type/baml_tests__compiles__comment_in_type__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/json_llm_return_type/baml_tests__compiles__json_llm_return_type__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/optional_function_parameters/baml_tests__compiles__optional_function_parameters__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/optional_function_parameters/baml_tests__compiles__optional_function_parameters__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/stream_llm_inferred_typeargs/baml_tests__compiles__stream_llm_inferred_typeargs__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_vibes_nested/baml_tests__compiles__testset_vibes_nested__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/type_builder_errors/baml_tests__compiles__type_builder_errors__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/type_builder_test/baml_tests__compiles__type_builder_test__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/generics/baml_tests__diagnostic_errors__generics__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/patterns_class_destructure_namespaces/baml_tests__diagnostic_errors__patterns_class_destructure_namespaces__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_textual.snap is excluded by !**/*.snap
📒 Files selected for processing (37)
  • baml_language/crates/baml_builtins2/baml_std/baml/ns_iter/iter.baml
  • baml_language/crates/baml_builtins2/src/lib.rs
  • baml_language/crates/baml_cli/tests/exit_code_e2e.rs
  • baml_language/crates/baml_compiler2_emit/src/emit.rs
  • baml_language/crates/baml_compiler2_emit/src/lib.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/src/associated_projection.rs
  • 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/inference.rs
  • baml_language/crates/baml_compiler2_tir/src/interfaces.rs
  • baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs
  • baml_language/crates/baml_fmt/src/lib.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_lsp2_actions/src/describe.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info_tests.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/iterator_generics_inference.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/stdlib_iterator_inference.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/const_binding.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_binding.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_reserved_binding_names.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/iterator/stdlib_iterator_errors.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/loops/const_for_loops.baml
  • baml_language/crates/baml_project/src/client_codegen.rs
  • baml_language/crates/baml_tests/baml_src/ns_exceptions/exceptions.baml
  • baml_language/crates/baml_tests/baml_src/ns_iter/iter.baml
  • baml_language/crates/baml_tests/baml_src/ns_iter_impl_generics_only/iter.baml
  • baml_language/crates/baml_tests/baml_src/ns_iter_impl_generics_only/ns_core/core.baml
  • baml_language/crates/baml_tests/tests/interfaces_associated_types.rs
  • baml_language/crates/baml_type/src/template.rs
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_vm/src/package_baml/json.rs
  • baml_language/crates/bex_vm/src/package_baml/mod.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/bex_vm_types/src/types.rs
💤 Files with no reviewable changes (9)
  • baml_language/crates/bex_vm/src/package_baml/mod.rs
  • baml_language/crates/baml_type/src/template.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info.rs
  • baml_language/crates/bex_vm_types/src/types.rs
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_vm/src/package_baml/json.rs
  • baml_language/crates/baml_tests/tests/interfaces_associated_types.rs
✅ Files skipped from review due to trivial changes (4)
  • baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/const_binding.baml
  • baml_language/crates/baml_builtins2/src/lib.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/loops/const_for_loops.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_binding.baml
🚧 Files skipped from review as they are similar to previous changes (12)
  • baml_language/crates/baml_fmt/src/lib.rs
  • baml_language/crates/baml_lsp2_actions/src/describe.rs
  • baml_language/crates/baml_compiler2_emit/src/emit.rs
  • baml_language/crates/baml_tests/baml_src/ns_iter/iter.baml
  • baml_language/crates/baml_cli/tests/exit_code_e2e.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info_tests.rs
  • baml_language/crates/baml_compiler2_tir/src/interfaces.rs
  • baml_language/crates/baml_compiler2_emit/src/lib.rs
  • baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/iterator_generics_inference.baml
  • baml_language/crates/baml_compiler2_tir/src/associated_projection.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
🛑 Comments failed to post (3)
baml_language/crates/baml_builtins2/baml_std/baml/ns_iter/iter.baml (2)

143-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't use null from at() as the end-of-stream sentinel.

This truncates iteration for nullable element types. ArrayIterator<int?>.new([1, null, 2]).collect() will stop at the in-bounds null and return [1]. Check idx against arr.length() before reading, then treat the fetched slot as data rather than EOF.

🤖 Prompt for 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.

In `@baml_language/crates/baml_builtins2/baml_std/baml/ns_iter/iter.baml` around
lines 143 - 149, The next(self) iterator uses self.arr.at(self.idx) and treats
null as Done which prematurely ends iteration for nullable element types; change
next to first check if self.idx >= self.arr.length() and return Done in that
case, otherwise read item = self.arr.at(self.idx), increment self.idx, and
return item (allowing the item to be null) so that in-bounds nulls are yielded
rather than terminating iteration (refer to function next, self.idx, self.arr.at
and arr.length()).

160-162: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard Range against non-progressing steps.

Range.new(0, 5, 0).collect() never terminates because next() keeps yielding the same value forever. Negative steps are also broken today because exhaustion only checks self.i < self.max. Either reject step <= 0 in new() or make next() branch on the sign of step.

Also applies to: 175-183

🤖 Prompt for 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.

In `@baml_language/crates/baml_builtins2/baml_std/baml/ns_iter/iter.baml` around
lines 160 - 162, Range currently allows step==0 and treats exhaustion only as
self.i < self.max, causing infinite loops and incorrect behavior for negative
steps; update Range.new to validate and throw for step == 0 (make new() return
throws on invalid args) and update next() to handle sign-aware exhaustion and
advancement: when step > 0 use self.i < self.max and increment by step, when
step < 0 use self.i > self.max and add step (negative) to i; reference the Range
struct fields i, max, step and the methods new() and next() so the checks reject
zero-steps and correctly support negative steps.
baml_language/crates/baml_compiler2_tir/src/generics.rs (1)

229-237: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize union members after lowering T? inside a union.

TypeExpr::Optional now returns Ty::nullable(...), but the TypeExpr::Union arm still wraps lowered members in Ty::Union directly. That makes A? | B lower to a nested union like Union([Union([A, null]), B]), so the new nullable_non_null_part() logic below never sees a top-level null member and flatten/dedup stops working.

Proposed fix
-        TypeExpr::Union {
-            variants: members, ..
-        } => Ty::Union(
-            members
-                .iter()
-                .map(|m| {
-                    lower_type_expr_with_generics(
-                        db,
-                        m,
-                        package_items,
-                        ns_context,
-                        bindings,
-                        diagnostics,
-                    )
-                })
-                .collect(),
-            TyAttr::default(),
-        ),
+        TypeExpr::Union {
+            variants: members, ..
+        } => normalize_union_members(
+            members.iter().map(|m| {
+                lower_type_expr_with_generics(
+                    db,
+                    m,
+                    package_items,
+                    ns_context,
+                    bindings,
+                    diagnostics,
+                )
+            }),
+            TyAttr::default(),
+        ),

Also applies to: 268-285

🤖 Prompt for 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.

In `@baml_language/crates/baml_compiler2_tir/src/generics.rs` around lines 229 -
237, The union lowering needs to normalize members after lowering so Optional ->
Ty::nullable doesn't produce nested Union nodes; in the TypeExpr::Union arm (and
similarly in the other union-handling block around the 268-285 region) collect
lowered members, iterate them and flatten any Ty::Union members into the
top-level member list, then deduplicate/null-normalize the list (so
nullable_non_null_part can detect a top-level null); finally construct the
resulting Ty::Union (or call the existing union construction helper) from the
flattened/deduped members instead of directly wrapping possibly-nested unions.

@aaronvg
aaronvg enabled auto-merge June 5, 2026 22:56
@aaronvg
aaronvg added this pull request to the merge queue Jun 5, 2026
Merged via the queue into canary with commit c957417 Jun 5, 2026
51 of 52 checks passed
@aaronvg
aaronvg deleted the aaron/fix-interfaces-and-describe branch June 5, 2026 23:04
pull Bot pushed a commit to justinlietz93/baml that referenced this pull request Jun 8, 2026
BEP-034 — concurrency via `spawn` / `await`. Single PR covering the
spawn-options core and the future combinators (supersedes BoundaryML#3596).

## Public BAML API (BEP-034 concurrency)

This is the complete user-facing surface this PR adds. Everything below
is callable from `.baml` source.

### Syntax

```baml
spawn { body }                       // -> Future<T, E>; runs body concurrently
await f                              // -> T; blocks until f settles, re-throws its error
spawn with t1 with t2 { body }       // middleware: applies transformers left-to-right
```

- `spawn { e }` evaluates to `Future<T, E>` where `T` is the body's
value type and `E` its throws type. The body starts running immediately;
the future is the handle.
- `await f` suspends until `f` settles, then yields its value or
re-throws its error. `await` **distributes over a union of futures**:
`await (Future<A,E1> | Future<B,E2>)` yields `A | B` (the error side is
contributed to the awaiter's throws surface).
- `spawn ... with <transformer> ...` runs each transformer over an
implicit `SpawnParams` before the runtime creates the future (see
middleware below).

### `baml.future` — futures & combinators

```baml
class Future<T, E> {
  fn cancel(self) -> bool throws never        // request cancel; false if already settled
  fn is_settled(self) -> bool throws never     // reached Ready | Error | Cancelled
  fn is_cancelled(self) -> bool throws never
  fn is_result(self) -> bool throws never      // settled with a value
  fn is_error(self) -> bool throws never        // settled with an error
  fn state(self) -> FutureState throws never
}

enum FutureState { Pending  Ready  Error  Cancelled }

// Combinators over a homogeneous Future<T,E>[] — each returns a new Future, so they compose.
fn all_complete<T, E>(futures: Future<T, E>[]) -> Future<T[], E>
//   all values in input order; losers KEEP RUNNING on failure; throws first error (input order)
fn all<T, E>(futures: Future<T, E>[]) -> Future<T[], E>
//   all values in input order; on first failure the rest are CANCELLED, then error re-thrown (JS Promise.all)
fn race<T, E>(futures: Future<T, E>[]) -> Future<T, E>
//   first to settle (success OR failure) wins; losers cancelled (JS Promise.race); empty array never settles
fn any<T, Err>(futures: Future<T, Err>[]) -> Future<T, AllFailed<Err>>
//   first to SUCCEED wins; losers cancelled; if all fail, throws AllFailed<Err> (JS Promise.any)

class AllFailed<E> { errors E[] }   // thrown by `any` when every input failed; errors in input order
```

### `baml.spawn` — spawn options & `with` middleware

```baml
// Every `spawn` implicitly builds a SpawnParams; the `with` pipeline transforms it.
// A transformer is any function (SpawnParams<T,E>) -> SpawnParams<U,F>.
class SpawnParams<T, E> {
  body   () -> T throws E
  name   string?
  group  TaskGroup?
  cancel CancelToken?
  detach bool
}

// The built-in config transformer. `cancel` links a token; `detach = true` opts out of the
// parent→child cancel cascade and routes unhandled errors to the root task; `group` enrolls
// the spawn in a TaskGroup (parks FIFO until admitted). Leaves `body`/types unchanged.
fn options<T, E>(group: TaskGroup? = null, cancel: CancelToken? = null, detach: bool? = null)
  -> (SpawnParams<T, E>) -> SpawnParams<T, E> throws never

class TaskGroup {                                  // caps concurrency; excess spawns queue FIFO
  fn new(limit: int, name: string? = null) -> TaskGroup throws never
  fn cancel(self, pending: bool? = true, active: bool? = true) -> int throws never  // returns # signalled
  fn set_limit(self, limit: int) -> void throws never   // raise admits queued; lower never preempts; 0 pauses
  fn limit(self) -> int throws never
  fn name(self) -> string? throws never
  fn active_count(self) -> int throws never
  fn queued_count(self) -> int throws never
}

class CancelToken {                                // cooperative, one-shot; fires at next await
  fn new() -> CancelToken throws never
  fn any(tokens: CancelToken[]) -> CancelToken throws never   // fires when ANY input fires (one-directional)
  fn cancel(self) -> int throws never              // 1 if this call transitioned it, else 0
  fn is_cancelled(self) -> bool throws never
}
```

A cancelled task throws `baml.panics.Cancelled` at its next `await`.

### Writing your own middleware

A transformer is just a function returning `(SpawnParams<T,E>) ->
SpawnParams<U,F>`. It can set config
fields, wrap `body` (retry/timing), or replace it entirely (a
type-changing transformer — e.g. a
fallback that catches and erases the error type to `null`):

```baml
function withFallback<T, E>(default_value: T) -> (SpawnParams<T, E>) -> SpawnParams<T, null> throws never {
  (params) -> {
    let original = params.body;
    SpawnParams {
      body: () -> { (original()) catch (e) { let e => default_value } },
      name: params.name, group: params.group, cancel: params.cancel, detach: params.detach,
    }
  }
}

// usage — `f : Future<int, null>`, so awaiting needs no catch:
let f = spawn with withFallback(99) { flaky() };
```

Transformers run **eagerly at the spawn site**, so a throwing
transformer throws there (and is part of
the caller's throws surface). Wrong-shaped transformers are rejected at
compile time with a concrete
"must return a SpawnParams" diagnostic.

## Spawn options
- `spawn ... with baml.spawn.options(group?, cancel?, detach?)` —
config-only `with` clause
- `CancelToken` (`new` / `any` / `cancel` / `is_cancelled`) +
parent→child cancel cascade
- `TaskGroup` rate limiting (FIFO admission, exact counts)
- `detach` (fresh cancel token; unhandled errors routed to the root
task)
- strict `with` validation (must be a `baml.spawn.SpawnConfig`)

## Future combinators (`baml.future`)
- `race` / `any` / `all` / `all_complete` — pure-BAML stdlib over a new
1-byte `AwaitAny` engine primitive (`__await_any: Future<T,E>[] -> int
throws never`, suspends until the first input settles)
- `AllFailed<E>` aggregate error class for `any`
- losers cancelled (`race`/`any`/`all`) or left running (`all_complete`)
per the BEP table

## Engine/runtime fixes found along the way
- **fire-and-forget errors (bug 2):** a child's unhandled throw is now
*deferred* (GC-rooted stash; future stays Pending with its wake signal
fired) and consumed at the single observation point (`future_ready`, now
`&mut`). The spawner's post-await drain surfaces only never-observed
errors — a sibling handling a child's error no longer races the spawner
into a spurious `UnhandledThrow`. (`tests/fire_and_forget.rs`)
- **AwaitAny index desync (bug 3):** global-index bookkeeping fix that
broke `any`
- **bare-bind catch arms (bug 4):** `let e =>` in a generic fn emitted a
constant-false `IsType` (TypeVar→Void erasure) and silently rethrew;
bare binds are irrefutable — no runtime test
- **copy propagation** substitutes `Terminator::Await` operands (`await
<param>` inside `.map` closures)
- unannotated lambda params infer from generic array elements mentioning
only the enclosing fn's rigid generics

## Generics: inferred type args at runtime
- TIR records call-site inferred bindings (`call_type_instantiations`);
MIR threads them to `frame.type_args` (`LoadType`/`TypeArgRef`) —
instances built inside generic fns carry real `class_type_args`, and
typed arms like `let e: AllFailed<string>` match
(`tests/generics_runtime.rs`)
- typed arms naming enclosing TypeVars (`let e: AllFailed<E>`) lower via
type-arg templates instead of erasing

## Canary merge (f5df38a)
Absorbs the spawn/GC permit deadlock fix (full `baml_tests` now
completes on this branch), BEP-044/BEP-57, instantiation expressions,
bounds, and interface-dispatch type-arg threading. Canary's strict
rigid-var checking exposed pre-existing inference gaps, fixed here:
- `await f` contributes the future's `E` to escaping throws
- callee generics matched only against rigid caller TypeVars now bind
(`map`'s `U:=T`)
- lambda *effective* throws feed callee throws-generics — lifts the
documented "current limitation" (covered callback throws no longer
false-positive) and violations name concrete types
- **miscompile fix:** `ShortCircuit` emit assumed a stack-carried
(PhiLike) destination; with `LoadType` before call args the carry is
validly rejected, so `a || b` as an inferred-generic call argument read
an uninitialized local. Emit now materializes the destination on the
short-circuit edge (no-op for PhiLike).
- std `any<T, E>` → `any<T, Err>` (works around generic-param name
capture vs `map<U, E>`; proper fix tracked separately)

## `with` middleware (c37027a)
Full BEP middleware: `spawn ... with a, b { body }` builds a
`SpawnParams<T,E>` and applies each transformer left-to-right — any
plain BAML function `(SpawnParams<T,E>) -> SpawnParams<U,F>`
(wrap/replace body, set config, change types). `baml.spawn.options` is
now a pure-BAML transformer (native SpawnConfig deleted). Flushed out +
fixed three language gaps: keyword segments in type paths, lambdas as
block tail expressions, and unannotated lambdas now INFER their throws
surface from the body (two tests asserting the old throws-never default
updated). Tests: `bex_engine/tests/middleware.rs` incl. the BEP's
`withRetry`.

## Complete bug ledger (every bug found & fixed on this branch)

**Engine / runtime**
1. **Fire-and-forget errors bypassed consumers** — a spawned child's
unhandled throw settled its Future eagerly AND queued on the spawner; a
sibling that awaited + handled the error was invisible to the engine, so
the spawner re-surfaced an already-handled error as `UnhandledThrow`.
Fixed with the deferred-error model: the error parks in a GC-rooted
stash, the future stays Pending (wake signal fired), `future_ready` is
the single observe-and-consume point, and the spawner's post-await drain
surfaces only never-observed errors.
2. **`AwaitAny` index desync** — global-index bookkeeping desync broke
`any` (wrong winner index reported).
3. **Copy propagation didn't substitute `Terminator::Await` operands** —
`await <param>` inside a `.map` closure read an uninitialized slot.
4. **`ShortCircuit` emit miscompile** — the emitter assumed the
destination is always stack-carried (PhiLike); when inferred-generic
calls push `LoadType` before args the carry is validly rejected, so `a
|| b` as a call argument left the value on the stack and read an
uninitialized local (`true || false` evaluated false). Emit now
materializes the destination on the short-circuit edge; MIR normalizes
projection destinations. *Latent before the canary merge — the suite
deadlocked before reaching the `||` fixtures.*

**Type system / generics**
5. **Bare-bind catch arms in generic fns always rethrew** — `let e =>`
emitted an `IsType` from its inferred type; a rigid TypeVar erases to
`Ty::Void` in MIR making the test constant-false. Bare binds are
irrefutable — no runtime test emitted (also drops tautological field
tests in destructures).
6. **Inferred call-site type args were discarded** — TIR ran inference,
substituted the return type, and threw the bindings away; MIR emitted
frame type args only for explicit `<...>` call sites. Instances built
inside generic fns carried empty `class_type_args`; typed arms like `let
e: AllFailed<string>` never matched. Bindings now recorded
(`call_type_instantiations`) and threaded to `frame.type_args`.
7. **Typed arms naming enclosing TypeVars compiled to constant-false** —
`let e: AllFailed<E>` inside `any<T, E>` erased E→Void; now lowers via
`TypeArgRef` templates resolved against the frame.
8. **Recorded instantiations poisoned by fresh literal types** —
`mk("hi")` recorded `T = Literal("hi")` so `is Box<string>` compared
unequal; recordings now `widen_fresh()`. Plus generic→generic chains
recorded `Unknown` (TypeVar actuals refused); fixed with an
allow-typevars backfill.
9. **Unannotated lambda params didn't infer from generic array
elements** whose type only mentions the enclosing fn's rigid generics.

**Throws tracking** *(pre-existing gaps exposed by canary's strict
rigid-var checking)*
10. **`await f` contributed nothing to escaping throws** — the future's
`E` is now part of the awaiting body's throw set.
11. **Callee generics matched only against rigid caller TypeVars never
bound** — `map`'s `U:=T` in `futures.map((f) -> { await f })` was
skipped as a TypeVar actual, leaving `U[]` in result types.
12. **Rigid TypeVar throw-facts collapsed lambda surfaces to `Never`** —
only genuinely open facts (fresh effect slots, Unknown) collapse now.
13. **Callee throws-generics fed from lambda SURFACE instead of
effective throws** — an omitted-throws lambda's open surface left
`map`'s `E` unbound (leaking a raw TypeVar) or symbolic where the body
demonstrably throws. Now fed from the side-table effective set: a
non-throwing callback binds `E:=never`, a throwing one binds its
concrete type. **Lifts a documented "current limitation"**: a callback
throw covered by the caller's declared throws no longer false-positives,
and violations name concrete types instead of symbolic `E`.

**Parser / lowering** *(flushed out by the middleware work)*
14. **`spawn`/`await` keywords rejected as path segments** in type
annotations (`baml.spawn.SpawnParams`) — parser type-path loop + CST
dotted-name extractor.
15. **A lambda as a block's tail expression was silently dropped** from
block elements — the block typed as void ("missing return value"); any
function returning a transformer hit it (the std's `json` code had been
dodging it with explicit `return`).
16. **Unannotated lambdas defaulted to `throws never` with a local
violation** — they now infer their throws surface from the body
(required for body wraps like `() -> { original() }` whose throws is a
generic `E`). Omitted throws on callback *params* remains
effect-polymorphic; explicit `throws` contracts remain enforced
(verified: E0096 fires through the forwarding chain — an enforcement
that itself depends on fixes 10–13).

**Known issues (documented, not fixed here)**
- Generic-param name capture: a callee's generic (`map<U, E>`) sharing a
name with a caller's generic false-binds through the
receiver-substituted signature. Worked around (`any<T, E>` → `any<T,
Err>`); proper fix is alpha-renaming on receiver substitution.
- `await` does not yet distribute over unions of futures (BEP
§await-distributes).

## `await` over unions (94d2b46)
Awaiting `Future<A, E1> | Future<B, E2>` types as `A | B` with both
error types tracked (BEP §await-distributes). Tests:
`bex_engine/tests/await_union.rs`.

## Review hardening (94d2b46)
CodeRabbit batch: `any` errors in INPUT order (+test) · explicit lambda
throws contracts preserved · `with`-transformer throws contributed at
spawn · copy-prop protects `await xs[_i]` · `TaskGroup.set_limit(+N)`
admits up to the cap (+test) · deferred-error hygiene on cancel/fulfill
· VM Spawn config validation · unified/completed generics walkers. Plus
the CI fix: `Object` enum exceeded its 64-byte budget under `heap_debug`
(`UnscheduledFuture` boxed) — the cause of the all-legs CI failure.

## Canary merge round 4 (8b2c403)
Canary's `c957417e7` ([codex] BoundaryML#3702) was a third independent
implementation of inferred-generic runtime type args; this merge
converges on it and fixes what the convergence broke:

1. **Adopted canary's CallPlan pipeline for inferred type args** —
removed our parallel `call_type_instantiations` TIR→MIR fallback (dead
code after the switch). Canary's ShortCircuit emit fix
(`store_on_taken_path`) is the same insight as ours; took theirs.
2. **`runtime_call_type_args` lost literal widening** — canary's
recording threads `T = literal "hi"` for `mk("hi")`, so an escaped
instance never matches `is Box<string>` (runtime class-arg comparison is
invariant). Restored `widen_fresh` on inferred bindings.
3. **Canary's stricter call-result recheck false-positived on `unknown`
holes** — `spawn with` middleware checks transformers against
`(SpawnParams<T,E>) -> SpawnParams<unknown, unknown>`; the normalizer
implements class-arg invariance as structural equality, so the holes
(and `BuiltinUnknown` from erased unresolved typevars, e.g. a
variable-bound transformer; and equivalent spellings like `T = int | 99`
from fallback-arg joins) all failed E0001. `is_subtype` now compares
same-class args pairwise: holes match anything, concrete args must be
**mutual** subtypes (invariance as equivalence). Concrete mismatches —
including canary's new iterator-stdlib diagnostics — still report.
4. **`heap_debug` feature gap** — `FutureRead::ErrorPending` arm added
to the heap debugger's validity walk.
5. **`task_group_cancel_cancels_members` timing** — the promptness bound
times `run_main` *including compilation*; the stdlib grew (ns_iter). 2s
→ 5s, still far under the 10s sleep it guards.

Closes the deferred interface-dispatch inferred-type-args item (canary's
BoundaryML#3702 ships it with runtime regressions).

Verified: `cargo test --workspace --all-features` — 185 suites / 0
failures (sdk_test_* excluded as in CI; they require cargo-nextest setup
scripts) + prek clean, rustc 1.93.0.

## Deferred (follow-up branch)
- `Result<T, E>` type design + `all_settled`, `.then()`
- cancellation-test panic-hook hygiene

Tests: `bex_engine` 227/0 (incl. `combinators`, `fire_and_forget`,
`generics_runtime`, `cancellation`, `task_group`); `baml_tests`
diagnostics 418/418, runtime fixtures 1650/1650 (one timing-sensitive
fixture flakes only under full parallel suite load); snapshots
regenerated.





<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* spawn ... with middleware and options(...) for spawn configuration;
CancelToken and TaskGroup APIs; future combinators all_complete, all,
race, any; await-any (returns winning input index)
* **Tests**
* Comprehensive BEP-034 tests for spawn options, TaskGroup, CancelToken,
combinators, middleware, fire-and-forget, and runtime behavior
* **Bug Fixes**
* Improved child-task error propagation and deferred fire-and-forget
error semantics across await/catch boundaries
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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