[codex] Fix inferred generic runtime type args#3702
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Wondering what really moved? Review this PR in Change Stack to inspect semantic changes, definitions, and references. 📝 WalkthroughWalkthroughAdds 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. ChangesCompiler and tooling integration
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
Binary size checks passed✅ 7 passed
Generated by |
There was a problem hiding this comment.
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 winAvoid lossy
zipwhen rendering function params.Using
param_names.zip(display_param_types)silently drops trailing params if those vectors ever diverge, yielding incomplete--listoutput (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 winExpand alias targets before collecting implicit class methods.
This helper only proceeds when
lower_type_expr_in_ns()returnsTy::Classdirectly.validate_implements_for()already accepts aliasfortargets by normalizing them through the alias-expansion path, soimplements 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 winUse the active generic bounds when comparing projected TIR types.
interface_tir_assoc_match()now relies ontir_types_equivalent(), buttir_types_equivalent()buildsAssociatedProjectionResolverwith&(). That means projections likeT::Itemonly 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 winAdd a unit test for the preserving-
Self.Assocpath.The added tests only cover the eager projection rewrite in
substitute_self_in. The new public helper's defining behavior is the opposite one—keepingSelf.Recordas a plainTypeExpr::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 winAdd local unit coverage for the frame-layout helpers.
runtime_call_type_args(),callee_frame_generic_params(), andruntime_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
⛔ Files ignored due to path filters (42)
baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__dispatch_keyword_class.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__dispatch_keyword_interfaces.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_class_shows_associated_type_bindings.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_interface.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_as.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_associated.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_blanket.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_bounds.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_check.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_class.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_extends.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_field.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_generic.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_generics.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_impl.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_implements.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_interface.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_interfaces.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_method.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_projection.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_requires.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_self.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_ts_interface.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_ts_new.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_type.snapis excluded by!**/*.snapbaml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_keyword_types.snapis excluded by!**/*.snapbaml_language/crates/baml_lsp2_actions/src/snapshots/baml_lsp2_actions__describe_tests__describe_interface.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/bigints.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/builtins.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/class_type_args_at_runtime.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/closures.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/deep_copy.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/fs.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/instantiation_expr.qualified.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/instantiation_expr.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/json_auto_derive.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/json_to_from_string.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/lambdas.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/reflect_type_of_generic.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/type_error_repro.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/type_reflection.snapis excluded by!**/*.snap
📒 Files selected for processing (40)
baml_language/crates/baml_builtins2/keyword_docs/baml_keywords.yamlbaml_language/crates/baml_builtins2/keyword_docs/ts_keywords.yamlbaml_language/crates/baml_cli/src/describe_command_tests.rsbaml_language/crates/baml_cli/src/project_load.rsbaml_language/crates/baml_cli/src/run_command.rsbaml_language/crates/baml_cli/tests/exit_code_e2e.rsbaml_language/crates/baml_compiler2_ast/src/auto_derive_json.rsbaml_language/crates/baml_compiler2_emit/src/emit.rsbaml_language/crates/baml_compiler2_emit/src/lib.rsbaml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_tir/src/associated_projection.rsbaml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_compiler2_tir/src/inference.rsbaml_language/crates/baml_compiler2_tir/src/interfaces.rsbaml_language/crates/baml_compiler2_tir/src/lib.rsbaml_language/crates/baml_compiler2_tir/src/lower_type_expr.rsbaml_language/crates/baml_exec/src/clap_target.rsbaml_language/crates/baml_fmt/src/ast/declarations.rsbaml_language/crates/baml_fmt/src/ast/mod.rsbaml_language/crates/baml_fmt/src/lib.rsbaml_language/crates/baml_lsp2_actions/src/check.rsbaml_language/crates/baml_lsp2_actions/src/describe.rsbaml_language/crates/baml_lsp2_actions/src/describe_tests.rsbaml_language/crates/baml_lsp2_actions/src/type_info.rsbaml_language/crates/baml_lsp2_actions/src/type_info_tests.rsbaml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/associated_type_projection_metadata.bamlbaml_language/crates/baml_tests/baml_src/ns_inferred_generic_type_args/inferred_generic_type_args.bamlbaml_language/crates/baml_tests/tests/interfaces.rsbaml_language/crates/baml_tests/tests/interfaces_associated_types.rsbaml_language/crates/baml_tests/tests/interfaces_class_generics.rsbaml_language/crates/bex_engine/src/conversion.rsbaml_language/crates/bex_engine/src/lib.rsbaml_language/crates/bex_vm/src/package_baml/json.rsbaml_language/crates/bex_vm/src/package_baml/mod.rsbaml_language/crates/bex_vm/src/vm.rsbaml_language/crates/bex_vm/tests/load_type.rsbaml_language/crates/bex_vm/tests/method_class_type_args.rsbaml_language/crates/bex_vm_types/src/types.rsbaml_language/sdk_tests/fixtures/type_shapes/baml_src/ns_generics/methods.bamlbaml_language/tools/speedtest/workloads/compute/generic-apply-inferred-1m.md
There was a problem hiding this comment.
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 winUse
generic_boundswhen validating unqualifiedT.Itemprojections.Line 2451 threads
generic_boundsintovalidate_unqualified_associated_type_projection(), but that helper still bails out onTy::TypeVar. The only typevar-specific projection check lives invalidate_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 forT.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
⛔ Files ignored due to path filters (4)
baml_language/crates/baml_tests/snapshots/baml_src/iter.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/json_to_from_string.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snapis excluded by!**/*.snap
📒 Files selected for processing (7)
baml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_lsp2_actions/src/check.rsbaml_language/crates/baml_tests/baml_src/ns_json_to_from_string/json_to_from_string.bamlbaml_language/crates/baml_tests/tests/interfaces.rsbaml_language/crates/baml_tests/tests/interfaces_associated_types.rsbaml_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
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ 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.
There was a problem hiding this comment.
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 winKeep IO builtin arity aligned with the new synthetic-count helper.
This helper now subtracts declared
typevalue params, butlower_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 runtimetypeoperands exist, so a builtin likefn 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 winDon't elide capped sys-op type args when they're
BuiltinUnknown.When
max_countisSome(k), the caller is explicitly requestingkpositional runtimetypeoperands. The fast-path at Lines 6118-6123 returns an empty vec if every inferred arg was rewritten toBuiltinUnknown, 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
⛔ Files ignored due to path filters (2)
baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snapis excluded by!**/*.snap
📒 Files selected for processing (1)
baml_language/crates/baml_compiler2_mir/src/lower.rs
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (33)
baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/_root.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/exceptions.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/iter.core.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/iter.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.core.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/type_reflection.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____03_hir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_tir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__testing_std__/baml_tests__compiles____testing_std____04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__testing_std__/baml_tests__compiles____testing_std____06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/comment_in_type/baml_tests__compiles__comment_in_type__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/json_llm_return_type/baml_tests__compiles__json_llm_return_type__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/optional_function_parameters/baml_tests__compiles__optional_function_parameters__04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/optional_function_parameters/baml_tests__compiles__optional_function_parameters__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/stream_llm_inferred_typeargs/baml_tests__compiles__stream_llm_inferred_typeargs__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/testset_vibes_nested/baml_tests__compiles__testset_vibes_nested__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/type_builder_errors/baml_tests__compiles__type_builder_errors__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/type_builder_test/baml_tests__compiles__type_builder_test__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/generics/baml_tests__diagnostic_errors__generics__04_tir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/patterns_class_destructure_namespaces/baml_tests__diagnostic_errors__patterns_class_destructure_namespaces__04_tir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_textual.snapis excluded by!**/*.snap
📒 Files selected for processing (37)
baml_language/crates/baml_builtins2/baml_std/baml/ns_iter/iter.bamlbaml_language/crates/baml_builtins2/src/lib.rsbaml_language/crates/baml_cli/tests/exit_code_e2e.rsbaml_language/crates/baml_compiler2_emit/src/emit.rsbaml_language/crates/baml_compiler2_emit/src/lib.rsbaml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_tir/src/associated_projection.rsbaml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_compiler2_tir/src/generics.rsbaml_language/crates/baml_compiler2_tir/src/inference.rsbaml_language/crates/baml_compiler2_tir/src/interfaces.rsbaml_language/crates/baml_compiler2_tir/src/lower_type_expr.rsbaml_language/crates/baml_fmt/src/lib.rsbaml_language/crates/baml_lsp2_actions/src/check.rsbaml_language/crates/baml_lsp2_actions/src/describe.rsbaml_language/crates/baml_lsp2_actions/src/type_info.rsbaml_language/crates/baml_lsp2_actions/src/type_info_tests.rsbaml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/iterator_generics_inference.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/stdlib_iterator_inference.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/const_binding.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_binding.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_reserved_binding_names.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/syntax/iterator/stdlib_iterator_errors.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/syntax/loops/const_for_loops.bamlbaml_language/crates/baml_project/src/client_codegen.rsbaml_language/crates/baml_tests/baml_src/ns_exceptions/exceptions.bamlbaml_language/crates/baml_tests/baml_src/ns_iter/iter.bamlbaml_language/crates/baml_tests/baml_src/ns_iter_impl_generics_only/iter.bamlbaml_language/crates/baml_tests/baml_src/ns_iter_impl_generics_only/ns_core/core.bamlbaml_language/crates/baml_tests/tests/interfaces_associated_types.rsbaml_language/crates/baml_type/src/template.rsbaml_language/crates/bex_engine/src/conversion.rsbaml_language/crates/bex_engine/src/lib.rsbaml_language/crates/bex_vm/src/package_baml/json.rsbaml_language/crates/bex_vm/src/package_baml/mod.rsbaml_language/crates/bex_vm/src/vm.rsbaml_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
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (33)
baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/_root.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/exceptions.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/iter.core.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/iter.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.core.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/type_reflection.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____03_hir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_tir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__testing_std__/baml_tests__compiles____testing_std____04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__testing_std__/baml_tests__compiles____testing_std____06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/comment_in_type/baml_tests__compiles__comment_in_type__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/json_llm_return_type/baml_tests__compiles__json_llm_return_type__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/optional_function_parameters/baml_tests__compiles__optional_function_parameters__04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/optional_function_parameters/baml_tests__compiles__optional_function_parameters__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/stream_llm_inferred_typeargs/baml_tests__compiles__stream_llm_inferred_typeargs__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/testset_vibes_nested/baml_tests__compiles__testset_vibes_nested__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/type_builder_errors/baml_tests__compiles__type_builder_errors__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/type_builder_test/baml_tests__compiles__type_builder_test__06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/generics/baml_tests__diagnostic_errors__generics__04_tir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/patterns_class_destructure_namespaces/baml_tests__diagnostic_errors__patterns_class_destructure_namespaces__04_tir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_textual.snapis excluded by!**/*.snap
📒 Files selected for processing (37)
baml_language/crates/baml_builtins2/baml_std/baml/ns_iter/iter.bamlbaml_language/crates/baml_builtins2/src/lib.rsbaml_language/crates/baml_cli/tests/exit_code_e2e.rsbaml_language/crates/baml_compiler2_emit/src/emit.rsbaml_language/crates/baml_compiler2_emit/src/lib.rsbaml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_tir/src/associated_projection.rsbaml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_compiler2_tir/src/generics.rsbaml_language/crates/baml_compiler2_tir/src/inference.rsbaml_language/crates/baml_compiler2_tir/src/interfaces.rsbaml_language/crates/baml_compiler2_tir/src/lower_type_expr.rsbaml_language/crates/baml_fmt/src/lib.rsbaml_language/crates/baml_lsp2_actions/src/check.rsbaml_language/crates/baml_lsp2_actions/src/describe.rsbaml_language/crates/baml_lsp2_actions/src/type_info.rsbaml_language/crates/baml_lsp2_actions/src/type_info_tests.rsbaml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/iterator_generics_inference.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/on_hover/stdlib_iterator_inference.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/const_binding.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_binding.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/syntax/expr/const_reserved_binding_names.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/syntax/iterator/stdlib_iterator_errors.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/syntax/loops/const_for_loops.bamlbaml_language/crates/baml_project/src/client_codegen.rsbaml_language/crates/baml_tests/baml_src/ns_exceptions/exceptions.bamlbaml_language/crates/baml_tests/baml_src/ns_iter/iter.bamlbaml_language/crates/baml_tests/baml_src/ns_iter_impl_generics_only/iter.bamlbaml_language/crates/baml_tests/baml_src/ns_iter_impl_generics_only/ns_core/core.bamlbaml_language/crates/baml_tests/tests/interfaces_associated_types.rsbaml_language/crates/baml_type/src/template.rsbaml_language/crates/bex_engine/src/conversion.rsbaml_language/crates/bex_engine/src/lib.rsbaml_language/crates/bex_vm/src/package_baml/json.rsbaml_language/crates/bex_vm/src/package_baml/mod.rsbaml_language/crates/bex_vm/src/vm.rsbaml_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 winDon't use
nullfromat()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-boundsnulland return[1]. Checkidxagainstarr.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 winGuard
Rangeagainst non-progressing steps.
Range.new(0, 5, 0).collect()never terminates becausenext()keeps yielding the same value forever. Negative steps are also broken today because exhaustion only checksself.i < self.max. Either rejectstep <= 0innew()or makenext()branch on the sign ofstep.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 winNormalize union members after lowering
T?inside a union.
TypeExpr::Optionalnow returnsTy::nullable(...), but theTypeExpr::Unionarm still wraps lowered members inTy::Uniondirectly. That makesA? | Blower to a nested union likeUnion([Union([A, null]), B]), so the newnullable_non_null_part()logic below never sees a top-levelnullmember 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.
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 -->

Summary
CallPlan.type_args.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
unknowngeneric parameters, causing instances created by inferred static constructors to carry incorrectclass_type_args, which then affected runtimeIsTypechecks 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 -- --nocapturecargo test -p baml_tests --test baml_src baml_test -- --nocapturegit diff --checkuvx prek run --all-filesNote
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.iterto the stdlib:Iterable/Iteratorwith associatedItemandError, default methods (map,collect, etc.), adapter classes, and a blanketIterableforT[].CLI and docs:
baml describegains interface/associated-type rendering and a large keyword guide (implements,associated,projection, …). TS crosswalk no longer mapsinterface/extends/implementsto “use composition”;newpoints at object-literal construction. Introspection treats abaml_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) viaAssociatedProjectionResolver, wired intobaml run --listand JSON listing so projections are not erased tovoid/unknown. Emit fixes short-circuit when the result lives in a real local (not stack-only), improves classIsType/ field lookup by module path, and aligns comments with TypeVar →BuiltinUnknownerasure. 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
baml.iterstandard library with Iterator/Iterable interfaces and adaptor types (Map, Filter, FlatMap, etc.).baml_src/directory detection.Improvements
describeand--listoutput with formatted type signatures and generic parameters.