Skip to content

implement typed rethrows for higher-order functions#3343

Open
rossirpaulo wants to merge 26 commits intocanaryfrom
rossirpaulo/typed-rethrows-hof
Open

implement typed rethrows for higher-order functions#3343
rossirpaulo wants to merge 26 commits intocanaryfrom
rossirpaulo/typed-rethrows-hof

Conversation

@rossirpaulo
Copy link
Copy Markdown
Contributor

@rossirpaulo rossirpaulo commented Apr 8, 2026

Summary

Implements typed rethrows for higher-order functions across the compiler2 pipeline, then tightens the semantics so the feature behaves correctly in the cases the original draft still got wrong.

This branch now carries throws through function values, propagates direct callback effects through higher-order wrappers, keeps stored callback positions closed by default, and fixes the contract-checking and alias-propagation gaps that were still present in the earlier Phase 4 / Phase 5 implementation.

What Changed

Shared Throws Semantics In TIR

  • Added a shared throws-semantics helper in baml_compiler2_tir and moved coverage logic there instead of duplicating it across builder-side checks and snapshot rendering.
  • Centralized:
    • alias-chain resolution for throws-bearing function types
    • flattening throws into semantic facts
    • subtype-aware throws coverage checks
    • reconstruction of concrete throws types for display and post-inference reporting
  • Updated TIR snapshot rendering to use structured Ty data rather than reparsing Ty::to_string().

Correct Throws Contract Checking

  • Replaced exact set-difference checking with semantic coverage-based checking for declared vs effective throws.
  • Fixed the enum bug where throws ErrorKind incorrectly failed against thrown variants like ErrorKind.NotFound.
  • Fixed related false-positive diagnostics:
    • bogus throws contract violation errors for broad enum declarations
    • bogus extraneous throws declaration warnings
    • downstream unreachable-arm warnings caused by treating broad enum facts as exact variant matches

Alias-Aware Higher-Order Rethrows

  • Fixed callback throws propagation for alias-typed function parameters such as:
    • type EnumThrower = () -> int throws ErrorKind
    • function use_enum_thrower(f: EnumThrower) -> int { f() }
  • TIR post-inference escaping-throws collection now unwraps function aliases before reading callee throws.
  • Pre-inference throw inference now also accounts for immediate direct-param callback calls, so named functions with alias-typed callbacks retain the right outward throws contract without creating recursive signature inference.

Stored Callback Enforcement

  • Kept the intended rule that stored function positions default closed unless the source explicitly widens them.
  • Tightened the dedicated stored-function diagnostic so it only fires when the function shapes otherwise match and the real mismatch is throws-specific.
  • Structural mismatches now surface as structural type errors instead of being masked by the stored-callback throws diagnostic.

Regression Suite Cleanup

  • Rewrote invalid “positive” tests into actual positive coverage:
    • compose_hof now returns the closure explicitly
    • returned_closures now returns closures explicitly instead of snapshotting missing-return errors
  • Added/updated coverage for:
    • enum/class/mixed throws through function aliases
    • returned closures that preserve throws contracts
    • stored local callback shape mismatch vs throws mismatch
  • Refreshed lexer/parser/HIR/TIR/MIR/diagnostics/codegen snapshots to match the corrected semantics.

Stdlib Boundary Policy

  • Kept the testing stdlib storage-boundary policy explicit in source.
  • Direct callback parameters remain effect-polymorphic.
  • Stored registry fields remain intentionally erased/closed at the storage boundary.

Architecture Notes

This stays aligned with baml_language/ARCHITECTURE.md:

  • semantics live in TIR, not in codegen or runtime
  • stdlib policy is expressed in BAML source rather than hidden downstream
  • shared internal helpers replace duplicated logic rather than adding a new compiler layer
  • the refactor reduces entropy instead of spreading throw semantics across builder, inference, and snapshot code independently

User / Developer Impact

  • Higher-order wrappers now preserve callback throws across direct callback parameters, including alias-typed ones.
  • Declaring throws ErrorKind now correctly covers thrown enum variants.
  • Stored callback positions still default to throws never, but the error message is now reserved for the actual throws-only mismatch case.
  • Regression coverage now validates the intended behavior instead of snapshotting several known-bad states.

Validation

  • cargo test -p baml_tests function_type_throws -- --nocapture
  • cargo test -p baml_tests testing_std -- --nocapture

Summary by CodeRabbit

  • New Features

    • Added end-user support for function throws clauses (e.g. () -> int throws string), shown in type display and propagated through calls, higher-order functions, optional calls, and catches.
    • Implicit effect polymorphism for callback parameters and improved diagnostics when storing a throwing function into a non-throwing position.
  • Tests

    • Expanded test suite covering parsing, inference, propagation, catch behavior, stored-callback enforcement, and many higher-order/edge cases.

rossirpaulo and others added 5 commits April 8, 2026 02:53
Add `throws: Box<Ty>` to `Ty::Function` across all compiler layers
(parser, CST, AST, TIR, normalization, MIR, runtime type) so the
type system can represent `() -> T throws E`. Defaults to `Never`
when omitted. Includes covariant subtyping for throws and test
coverage for the new syntax.
Preserve throw contracts when functions are used as values: free
functions, class methods, and builtins now include their declared
throws in the constructed Ty::Function. Lambda bodies infer throws
from their expressions (or validate against explicit annotations).
Adds comprehensive test coverage for lambda inference, explicit
annotations, contract violations, nested lambdas, HOF patterns,
function declarations, type aliases, and class field function types.
Implement Phase 4 of typed rethrows for higher-order functions:

- Add StoredFunctionRequiresExplicitThrows diagnostic for when a
  throwing function is assigned to a stored position typed throws never
- Enforce throws compatibility in lambda bidirectional checking
- Resolve type aliases before lambda checking to handle type alias cases
- Add comprehensive test coverage for class fields, local variables,
  type aliases, and return positions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 8, 2026

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

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Apr 10, 2026 4:51pm
promptfiddle Ready Ready Preview, Comment Apr 10, 2026 4:51pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds first-class support for optional function-type "throws" clauses across parser, AST, lowering, TIR/MIR Ty representations, inference/builder, throws semantics/contract utilities, normalization/subtyping, diagnostics/LSP display, and a large test suite; updates builtin test registry signatures to include throws unknown.

Changes

Cohort / File(s) Summary
Parser & Syntax
baml_language/crates/baml_compiler_parser/src/parser.rs, baml_language/crates/baml_compiler_syntax/src/ast.rs
Parser recognizes optional throws clauses; TypeExpr exposes function_throws_type() accessor.
AST & Validation
baml_language/crates/baml_compiler2_ast/src/ast.rs, .../lib.rs, .../disambiguate.rs
TypeExpr::Function and span-stripping/validation now include an optional throws subtree.
TypeExpr Lowering / Fn Context
baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs, baml_language/crates/baml_tests/src/compiler2_tir/mod.rs
Added FnTypeLoweringContext and lower_type_expr_with_fn_context(); synthesizes effect vars for omitted throws in callback param root positions and threads synthetic vars for rendering.
Core Ty / Display / Runtime
baml_language/crates/baml_compiler2_tir/src/ty.rs, baml_language/crates/baml_type/src/lib.rs
Ty::Function extended with throws; Display, parenthesization rules, and runtime validation updated to include throws.
MIR lowering & MIR types
baml_language/crates/baml_compiler2_mir/src/lower.rs
Maps TIR function throws into MIR function representation, treating Void/Never as absent.
Inference / Builder / Scope
baml_language/crates/baml_compiler2_tir/src/builder.rs, .../inference.rs, .../infer_context.rs
Inference builder returns catch_residual_throws; lambdas return effective_throws_ty; added stored-function throws validation error variant; scope stores aliases and catch residuals; added throws-aware mismatch reporting and call-site alias resolution.
Effective throws collection
baml_language/crates/baml_compiler2_tir/src/effective_throws.rs
New module walking expressions/statements to collect effective thrown-type facts (collect_effective_throws and recursive walkers).
Throws semantics & contract utils
baml_language/crates/baml_compiler2_tir/src/throws_semantics.rs, .../throw_inference.rs
New utilities for alias resolution, flattening types to throw facts, contract diffs, combining effect vars into concrete throws types; throw_inference delegates flattening and collects param-call-derived throws.
Generics & Normalization
baml_language/crates/baml_compiler2_tir/src/generics.rs, .../normalize.rs
Generics substitution, contains-typevar checks, binding inference, erasure, normalization, and function subtyping extended to handle throws (covariant).
Parser/HIR formatting & LSP display
baml_language/crates/baml_compiler2_hir/src/builder.rs, baml_language/crates/baml_lsp2_actions/src/utils.rs
Adjusted parenthesization for option/list of unions/functions; updated display to include throws clause when present.
Diagnostics / LSP mapping
baml_language/crates/baml_lsp2_actions/src/check.rs
Maps new StoredFunctionRequiresExplicitThrows error to a diagnostic ID.
Crate exports
baml_language/crates/baml_compiler2_tir/src/lib.rs
Publicly re-exported effective_throws and throws_semantics modules.
Builtin registry change
baml_language/crates/baml_builtins2/baml_std/testing/registry.baml
Updated TestRegistration.body and TestSetRegistration.collector field types to include throws unknown.
Tests / Fixtures
baml_language/crates/baml_tests/projects/function_type_throws/* (many files), baml_language/crates/baml_lsp2_actions_tests/*
Large suite of new tests and many updated test expectations covering parsing, inference, lambda/callback/HO functions, optional calls, catch semantics, stored-callback enforcement, class fields, aliases, enums/classes, arrays/maps, and negative cases.
Type-checker/display helpers
baml_language/crates/baml_tests/src/compiler2_tir/mod.rs, baml_language/crates/baml_lsp2_actions/src/utils.rs
Rendering logic updated to show synthesized/derived throws, combine synthetic effect vars with body throws for display.

Sequence Diagram

sequenceDiagram
    participant Parser as Parser
    participant AST as AST/TypeExpr
    participant Lower as Type Lowering
    participant TySys as Ty System
    participant Inference as Type Inference
    participant Throws as Throws Semantics

    Parser->>AST: parse function type (optional "throws")
    AST->>Lower: provide TypeExpr::Function (includes optional throws)
    Lower->>TySys: lower_type_expr_with_fn_context (synthesize __throws_<param> if omitted)
    TySys->>TySys: construct Ty::Function { params, ret, throws }
    Inference->>TySys: infer_lambda_body -> (ret_ty, effective_throws_ty, expressions)
    Inference->>Throws: collect_effective_throws(expressions, catch_residual_throws, aliases)
    Throws->>Inference: return effective throw facts / concrete throws Ty
    Inference->>Inference: validate declared vs effective throws -> emit diagnostics if mismatch
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I nibbled syntax, stitched throws to each sign,

lambdas whisper secrets in effect-var design.
Catches now count what may leap from the nest,
tests bloom like clover while the compiler rests.
Hop—types keep watch as we tidy the nest!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'implement typed rethrows for higher-order functions' directly and clearly reflects the main objective of the changes, which is to implement a typed rethrows system for higher-order functions across the compiler pipeline.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rossirpaulo/typed-rethrows-hof

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

Binary size checks passed

5 passed

Artifact Platform Gzip Baseline Delta Status
bridge_cffi Linux 5.8 MB 5.7 MB +88.5 KB (+1.6%) OK
bridge_cffi-stripped Linux 5.8 MB 5.7 MB +67.9 KB (+1.2%) OK
bridge_cffi macOS 4.7 MB 4.6 MB +118.3 KB (+2.6%) OK
bridge_cffi-stripped macOS 4.7 MB 4.7 MB +60.1 KB (+1.3%) OK
bridge_wasm WASM 3.1 MB 3.0 MB +96.9 KB (+3.3%) OK

Generated by cargo size-gate · workflow run

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 8, 2026

Merging this PR will degrade performance by 18.49%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 10 regressed benchmarks
✅ 5 untouched benchmarks
⏩ 105 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
WallTime bench_incremental_no_change 5.1 ms 5.8 ms -13.13%
WallTime bench_incremental_modify_function 6.7 ms 7.6 ms -11.95%
WallTime bench_scale_100_functions 52.2 ms 60.2 ms -13.41%
WallTime bench_parse_only_simple 28.5 µs 34.9 µs -18.49%
WallTime bench_scale_deep_nesting 17 ms 19.6 ms -13.4%
WallTime bench_incremental_rename_type 21 ms 24.3 ms -13.81%
WallTime bench_single_simple_file 10.9 ms 12.4 ms -11.81%
WallTime bench_lexer_only_simple 16.5 µs 19.7 µs -16.33%
WallTime bench_empty_project 10.1 ms 11.2 ms -10.08%
WallTime bench_incremental_add_new_file 8.4 ms 9.4 ms -10.13%

Comparing rossirpaulo/typed-rethrows-hof (73cdd0f) with canary (e817f17)

Open in CodSpeed

Footnotes

  1. 105 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@rossirpaulo rossirpaulo changed the title [codex] implement typed rethrows for higher-order functions implement typed rethrows for higher-order functions Apr 8, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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 (2)
baml_language/crates/baml_tests/src/compiler2_tir/mod.rs (1)

1113-1122: ⚠️ Potential issue | 🟡 Minor

Use the post-inference result in the declared-throws branch too.

This branch still renders infers ... from the pre-inference throw graph only. Any function with an explicit throws and HOF-driven body effects will snapshot stale or missing inferred throws even though the post-inference result is already available.

🩹 Minimal fix
-                            match &inferred_throws {
-                                Some(inferred) => {
-                                    format!(" throws {declared} infers {inferred}")
-                                }
-                                None => format!(" throws {declared}"),
-                            }
+                            match post_inference_throws
+                                .as_deref()
+                                .or(inferred_throws.as_deref())
+                            {
+                                Some(inferred) => {
+                                    format!(" throws {declared} infers {inferred}")
+                                }
+                                None => format!(" throws {declared}"),
+                            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_tests/src/compiler2_tir/mod.rs` around lines 1113 -
1122, The declared-throws branch builds its message using a stale pre-inference
throws snapshot; update the branch that handles sig.throws so it uses the
post-inference inferred result instead of the old snapshot. Concretely, after
calling lower_type_expr_in_ns to get declared, change the match on
inferred_throws to reference the same post-inference inferred-throws variable
used elsewhere (the post-inference binding), so the format!(" throws {declared}
infers {inferred}") uses the up-to-date inferred_throws value rather than the
pre-inference value; keep lower_type_expr_in_ns, sig.throws and the declared
variable as-is.
baml_language/crates/baml_compiler2_tir/src/builder.rs (1)

4079-4103: ⚠️ Potential issue | 🟠 Major

Builtin method function types drop declared throws.

This branch computes throws_ty solely from synthetic_effect_vars and never consults sig.throws. Any builtin method with an explicit throws clause is therefore exposed as throws never unless it also happens to be effect-polymorphic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_compiler2_tir/src/builder.rs` around lines 4079 -
4103, The current branch builds throws_ty solely from synthetic_effect_vars,
ignoring any explicit throws in sig.throws; update the logic that computes
throws_ty (the match on synthetic_effect_vars and the constructed Ty::Never /
Ty::TypeVar / Ty::Union) to also incorporate sig.throws: if sig.throws is
present, map each declared throw into the appropriate Ty (similar mapping used
for synthetic_effect_vars), merge those mapped types with the
synthetic-effect-derived types into a single union (or single type when only
one), and only produce Ty::Never when both synthetic_effect_vars and sig.throws
are empty; keep the returned BuiltinResolution::Method construction
(params/ret/throws) unchanged apart from using this merged throws_ty. Ensure you
still drop(diags) as before.
🧹 Nitpick comments (4)
baml_language/crates/baml_lsp2_actions/src/check.rs (1)

302-303: Add a focused unit test for this new error-to-diagnostic mapping.

This new TirTypeError arm should be covered by a local unit test for tir_type_error_to_diagnostic_id to guard against future enum/mapping drift.

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

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_lsp2_actions/src/check.rs` around lines 302 - 303,
Add a focused Rust unit test that constructs a
TirTypeError::StoredFunctionRequiresExplicitThrows variant and asserts that
calling tir_type_error_to_diagnostic_id on it returns
DiagnosticId::TypeMismatch; place the test in the same module (e.g., a
#[cfg(test)] mod tests) in check.rs so it’s a local unit test, give it a
descriptive name like
stored_function_requires_explicit_throws_maps_to_type_mismatch, create any
minimal required dummy values to build the TirTypeError variant, call
tir_type_error_to_diagnostic_id(error) and use assert_eq! to verify the result.
baml_language/crates/baml_compiler_parser/src/parser.rs (1)

2014-2020: Add a parser unit test for function-type throws syntax.

Please add a local parser test that asserts (int) -> string throws MyErr (and a malformed variant) parses into THROWS_CLAUSE correctly.

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

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_compiler_parser/src/parser.rs` around lines 2014 -
2020, Add Rust unit tests in the same module to cover function-type throws
parsing: write a test that feeds "(int) -> string throws MyErr" to the parser
(using the existing parsing helper used in this file, e.g., calling
Parser::new(...).parse_type() or the local test helper) and assert the resulting
syntax tree contains a THROWS_CLAUSE node with the throws token and the
identifier child for MyErr; also add a second test for a malformed case like
"(int) -> string throws" that asserts a THROWS_CLAUSE node is still produced
(but missing identifier) so the parser recovers; place tests in the module's
#[cfg(test)] tests block alongside other parser unit tests.
baml_language/crates/baml_compiler_syntax/src/ast.rs (1)

2763-2769: Consider merging with the existing impl TypeExpr block.

This adds a second impl TypeExpr block while there's already a comprehensive one at lines 302-658 containing all other function-type accessors (is_function_type, function_type_params, function_return_type). Moving function_throws_type() near those related methods (around line 648) would improve discoverability and maintain consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_compiler_syntax/src/ast.rs` around lines 2763 -
2769, The new method function_throws_type() is in a separate impl block for
TypeExpr; move it into the existing impl TypeExpr that already contains
is_function_type, function_type_params, and function_return_type so all
function-type accessors are grouped together—specifically relocate the
function_throws_type method into that impl (near the other function_* accessors)
and remove the now-empty/duplicate impl block to keep discoverability and
consistency.
baml_language/crates/baml_tests/src/compiler2_tir/mod.rs (1)

1095-1111: Keep post_inference_throws as Ty until the final render.

Stringifying here forces the later split(" | ") merge, which is lossy for any single thrown type whose Display itself contains |. Keeping the structured Ty avoids reparsing and keeps snapshot output faithful.

💡 Safer shape
-                        let post_inference_throws: Option<String> = {
+                        let post_inference_throws: Option<baml_compiler2_tir::ty::Ty> = {
                             if let Some(ref fb) = func_body_opt {
                                 if let baml_compiler2_hir::body::FunctionBody::Expr(ref body) = **fb
                                 {
                                     let ty = inference.effective_throws(db, pkg_id, body);
                                     if matches!(ty, baml_compiler2_tir::ty::Ty::Never { .. }) {
                                         None
                                     } else {
-                                        Some(ty.to_string())
+                                        Some(ty)
                                     }
                                 } else {
                                     None
                                 }
                             } else {
                                 None
                             }
                         };

Then flatten/dedupe the Ty facts when assembling throws_str, instead of reparsing Display.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_tests/src/compiler2_tir/mod.rs` around lines 1095 -
1111, The code currently stringifies the inferred throw type into
post_inference_throws (via ty.to_string()), which loses structure and causes
later reparsing/splitting (split(" | ")) to be lossy when a type's Display
contains '|'; change post_inference_throws to keep the structured
baml_compiler2_tir::ty::Ty (i.e., Option<Ty>) instead of Option<String> in the
block that inspects FunctionBody::Expr and calls inference.effective_throws,
stop calling ty.to_string() there, and then update the later throws_str assembly
logic to accept Option<Ty>, flatten and deduplicate Ty facts directly (rather
than reparsing Display output) before final rendering to string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@baml_language/crates/baml_compiler2_mir/src/lower.rs`:
- Around line 167-182: In the Tir2Ty::Function -> Ty::Function arm, normalize
throws aliases that resolve to never by first converting the throws type with
convert_tir2_ty(...) and then checking the converted Ty for Ty::Never; if it is
Ty::Never return None (closed throws), otherwise wrap the converted Ty in
Some(Box::new(...)). Apply this change in the throws handling of the
Tir2Ty::Function match arm so alias-to-never gets treated the same as a direct
Tir2Ty::Never.

In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 4489-4523: In report_type_mismatch, the stored-function throws
diagnostic is emitted too eagerly; before taking the Ty::Function throws branch
(involving expected_resolved/got_resolved and their throws fields), verify that
the function shapes (parameter types and return type) are already compatible
(e.g., compare params length and elementwise compatibility and return type
compatibility after resolve_alias_chain); only if the shapes match and
expected_throws is Ty::Never while actual_throws is not, emit
TirTypeError::StoredFunctionRequiresExplicitThrows, otherwise fall through to
emit the regular TypeMismatch diagnostic as before.
- Around line 2815-2845: infer_concrete_body_throws currently strips all
Ty::TypeVar and combine_effect_vars_with_body_throws unconditionally re-adds
every synthetic_effect_var when any callback exists, producing incorrect throws;
change the flow so infer_concrete_body_throws preserves concrete thrown types
from the body's effective_throws (do not drop non-concrete facts) and also
return which synthetic effect vars actually escape the body (i.e., which TypeVar
names appear in the body's effective facts), then update
combine_effect_vars_with_body_throws to only include those synthetic_effect_vars
that the body actually escapes and to merge concrete body_throws_facts with
those vars (never eliding concrete throws into Ty::Never), keeping the Union
semantics when there are multiple concrete/var throws; use the existing symbols
infer_concrete_body_throws, combine_effect_vars_with_body_throws,
synthetic_effect_vars and body_throws_facts to locate and implement this change.
- Around line 720-744: The code determines throws_ty from func_def.throws or
e_body but never validates the final lambda function type against the expected
callback type; when func_def.throws is present the function can claim narrower
throws/return types incorrectly. After computing ret_ty and throws_ty in
infer_lambda_body / the lambda builder (where ret_ty, e_body, throws_ty,
func_def are used), perform a final validation step that resolves the
constructed function type (including throws_ty and ret_ty) and checks it is a
subtype of the expected_resolved/expected callback type (use the existing
subtype/unify helper used elsewhere in this module), report a diagnostic via
self.context.report_at_span on mismatch, and fall back or adjust the returned
type accordingly so annotated throws/return types cannot be narrower than the
body or the expected callback type.

In `@baml_language/crates/baml_compiler2_tir/src/effective_throws.rs`:
- Around line 387-412: Expr::OptionalCall currently only recurses into the
callee and args and thus drops throws produced by actually invoking the optional
function; update the Expr::OptionalCall branch in
collect_effective_throws_from_expr to also call
collect_effective_throws_from_call (the same way Expr::Call does) so invocation
throws are included, and ensure the call-handling code can accept an optional
function type by unwrapping Ty::Optional(Box<Ty::Function>) (either before
calling collect_effective_throws_from_call or inside it) so f?.() and optional
method calls record escaping throws correctly.

In `@baml_language/crates/baml_type/src/lib.rs`:
- Around line 164-169: The Function variant of Ty now has a throws field but
both validate_runtime and the fmt::Display arm for Ty::Function still ignore it;
update validate_runtime's Ty::Function match to call validate_runtime() on
throws when Some(...) and update the fmt::Display match for Ty::Function to
include " throws <T>" in the printed output when throws.is_some() (build the
param list from params and append " throws <throws>" if present), referencing
the Ty::Function variant, the validate_runtime function and the fmt::Display
impl so the runtime validation and debug rendering include the throws type.

---

Outside diff comments:
In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 4079-4103: The current branch builds throws_ty solely from
synthetic_effect_vars, ignoring any explicit throws in sig.throws; update the
logic that computes throws_ty (the match on synthetic_effect_vars and the
constructed Ty::Never / Ty::TypeVar / Ty::Union) to also incorporate sig.throws:
if sig.throws is present, map each declared throw into the appropriate Ty
(similar mapping used for synthetic_effect_vars), merge those mapped types with
the synthetic-effect-derived types into a single union (or single type when only
one), and only produce Ty::Never when both synthetic_effect_vars and sig.throws
are empty; keep the returned BuiltinResolution::Method construction
(params/ret/throws) unchanged apart from using this merged throws_ty. Ensure you
still drop(diags) as before.

In `@baml_language/crates/baml_tests/src/compiler2_tir/mod.rs`:
- Around line 1113-1122: The declared-throws branch builds its message using a
stale pre-inference throws snapshot; update the branch that handles sig.throws
so it uses the post-inference inferred result instead of the old snapshot.
Concretely, after calling lower_type_expr_in_ns to get declared, change the
match on inferred_throws to reference the same post-inference inferred-throws
variable used elsewhere (the post-inference binding), so the format!(" throws
{declared} infers {inferred}") uses the up-to-date inferred_throws value rather
than the pre-inference value; keep lower_type_expr_in_ns, sig.throws and the
declared variable as-is.

---

Nitpick comments:
In `@baml_language/crates/baml_compiler_parser/src/parser.rs`:
- Around line 2014-2020: Add Rust unit tests in the same module to cover
function-type throws parsing: write a test that feeds "(int) -> string throws
MyErr" to the parser (using the existing parsing helper used in this file, e.g.,
calling Parser::new(...).parse_type() or the local test helper) and assert the
resulting syntax tree contains a THROWS_CLAUSE node with the throws token and
the identifier child for MyErr; also add a second test for a malformed case like
"(int) -> string throws" that asserts a THROWS_CLAUSE node is still produced
(but missing identifier) so the parser recovers; place tests in the module's
#[cfg(test)] tests block alongside other parser unit tests.

In `@baml_language/crates/baml_compiler_syntax/src/ast.rs`:
- Around line 2763-2769: The new method function_throws_type() is in a separate
impl block for TypeExpr; move it into the existing impl TypeExpr that already
contains is_function_type, function_type_params, and function_return_type so all
function-type accessors are grouped together—specifically relocate the
function_throws_type method into that impl (near the other function_* accessors)
and remove the now-empty/duplicate impl block to keep discoverability and
consistency.

In `@baml_language/crates/baml_lsp2_actions/src/check.rs`:
- Around line 302-303: Add a focused Rust unit test that constructs a
TirTypeError::StoredFunctionRequiresExplicitThrows variant and asserts that
calling tir_type_error_to_diagnostic_id on it returns
DiagnosticId::TypeMismatch; place the test in the same module (e.g., a
#[cfg(test)] mod tests) in check.rs so it’s a local unit test, give it a
descriptive name like
stored_function_requires_explicit_throws_maps_to_type_mismatch, create any
minimal required dummy values to build the TirTypeError variant, call
tir_type_error_to_diagnostic_id(error) and use assert_eq! to verify the result.

In `@baml_language/crates/baml_tests/src/compiler2_tir/mod.rs`:
- Around line 1095-1111: The code currently stringifies the inferred throw type
into post_inference_throws (via ty.to_string()), which loses structure and
causes later reparsing/splitting (split(" | ")) to be lossy when a type's
Display contains '|'; change post_inference_throws to keep the structured
baml_compiler2_tir::ty::Ty (i.e., Option<Ty>) instead of Option<String> in the
block that inspects FunctionBody::Expr and calls inference.effective_throws,
stop calling ty.to_string() there, and then update the later throws_str assembly
logic to accept Option<Ty>, flatten and deduplicate Ty facts directly (rather
than reparsing Display output) before final rendering to string.
🪄 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: 53c25173-4cf1-48ed-bfaa-0e18ef636d02

📥 Commits

Reviewing files that changed from the base of the PR and between 617658a and 4282b74.

⛔ Files ignored due to path filters (79)
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__testing_std__/baml_tests____testing_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__testing_std__/baml_tests____testing_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/builtin_io/baml_tests__builtin_io__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/catch_all_keyword/baml_tests__catch_all_keyword__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/catch_throw/baml_tests__catch_throw__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/catch_throw_regressions/baml_tests__catch_throw_regressions__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/format_checks/baml_tests__format_checks__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__array_map_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__basic.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__catch_absorbs_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__chained_hof.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__class_field_fn_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__compose_hof.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__enum_class_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__explicit_param_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__fn_decl_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__fn_type_alias_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__hof_own_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__hof_rethrows.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__hof_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__lambda_throws_explicit.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__lambda_throws_infer.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__lambda_throws_violation.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__mixed_params.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__nested_lambda_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__returned_closures.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__stored_callback_enforcement.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__array_map_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__basic.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__catch_absorbs_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__chained_hof.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__class_field_fn_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__compose_hof.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__enum_class_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__explicit_param_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__fn_decl_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__fn_type_alias_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__hof_own_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__hof_rethrows.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__hof_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__lambda_throws_explicit.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__lambda_throws_infer.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__lambda_throws_violation.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__mixed_params.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__nested_lambda_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__returned_closures.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__stored_callback_enforcement.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__array_map_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__basic.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__catch_absorbs_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__chained_hof.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__class_field_fn_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__compose_hof.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__enum_class_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__explicit_param_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__fn_decl_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__fn_type_alias_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__hof_own_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__hof_rethrows.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__hof_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__lambda_throws_explicit.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__lambda_throws_infer.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__lambda_throws_violation.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__mixed_params.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__nested_lambda_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__returned_closures.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__stored_callback_enforcement.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_advanced/baml_tests__lambda_advanced__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_basic/baml_tests__lambda_basic__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_errors/baml_tests__lambda_errors__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/null_handling/baml_tests__null_handling__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/type_annotation/baml_tests__type_annotation__04_tir.snap is excluded by !**/*.snap
📒 Files selected for processing (39)
  • baml_language/crates/baml_builtins2/baml_std/testing/registry.baml
  • baml_language/crates/baml_compiler2_ast/src/ast.rs
  • baml_language/crates/baml_compiler2_ast/src/lib.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_type_expr.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/effective_throws.rs
  • baml_language/crates/baml_compiler2_tir/src/generics.rs
  • baml_language/crates/baml_compiler2_tir/src/infer_context.rs
  • baml_language/crates/baml_compiler2_tir/src/inference.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_compiler2_tir/src/normalize.rs
  • baml_language/crates/baml_compiler2_tir/src/ty.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_compiler_syntax/src/ast.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_tests/projects/function_type_throws/array_map_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/basic.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/catch_absorbs_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/chained_hof.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/class_field_fn_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/compose_hof.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/enum_class_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/explicit_param_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/fn_decl_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/fn_type_alias_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/hof_own_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/hof_rethrows.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/hof_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/lambda_throws_explicit.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/lambda_throws_infer.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/lambda_throws_violation.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/mixed_params.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/nested_lambda_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/returned_closures.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/stored_callback_enforcement.baml
  • baml_language/crates/baml_tests/src/compiler2_tir/mod.rs
  • baml_language/crates/baml_type/src/lib.rs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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_compiler2_tir/src/throw_inference.rs (1)

69-79: ⚠️ Potential issue | 🟠 Major

Pull dependency-exported aliases into this pre-inference alias map.

Line 69 only collects aliases from the current package, even though the dependency interfaces are already available on Lines 71-79. If a callback parameter is typed as an exported dependency alias to a function type, function_throws_facts cannot unwrap it here, so collect_direct_param_call_throws drops the callback's direct throws before the graph is analyzed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_compiler2_tir/src/throw_inference.rs` around lines
69 - 79, The pre-inference alias map currently only includes local aliases from
collect_type_aliases(db, pkg_items) so exported type aliases from dependencies
(available in dep_interfaces) are missed; update the alias map to merge in
dependency-exported aliases before running
function_throws_facts/collect_direct_param_call_throws by iterating
dep_interfaces (the (Name, &crate::package_interface::PackageInterface) pairs
returned by package_dependencies + package_interface::package_interface) and
pulling each dependency's exported type aliases into the same aliases structure
so callback parameter types that reference exported dependency aliases resolve
correctly during throw inference.
🧹 Nitpick comments (2)
baml_language/crates/baml_tests/projects/function_type_throws/fn_decl_throws.baml (1)

15-24: Consider avoiding constant inputs to strengthen rethrows assertions.

Using declared_may_fail(1) is valid, but parameterizing the input can make this fixture more resilient if path-sensitive effect narrowing is added later.

Optional robustness tweak
-function caller() -> string {
-  declared_may_fail(1)
+function caller(x: int) -> string {
+  declared_may_fail(x)
 }
@@
-function safe_caller() -> string {
-  declared_may_fail(1) catch (e) {
+function safe_caller(x: int) -> string {
+  declared_may_fail(x) catch (e) {
     _ => "caught"
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@baml_language/crates/baml_tests/projects/function_type_throws/fn_decl_throws.baml`
around lines 15 - 24, The test currently calls declared_may_fail(1) with a
hard-coded constant which weakens rethrow/effect assertions; change caller and
safe_caller to accept an input parameter and forward that parameter to
declared_may_fail instead (update function signatures of caller and safe_caller
and the call sites inside them to use the parameter), keeping the catch block in
safe_caller intact; this makes the fixture use a variable input to
declared_may_fail rather than the constant 1 and preserves the intended
behaviors of declared_may_fail, caller, and safe_caller.
baml_language/crates/baml_tests/projects/function_type_throws/optional_call_throws.baml (1)

13-15: LGTM!

Integration test correctly exercises the rethrows path by supplying a throwing callback whose throws string type is inferred from the throw "boom" expression.

Consider adding a companion test function that exercises optional_call_caught to verify the catch path end-to-end:

💡 Optional: add catch-path integration test
 function test_optional_call_with_throwing_callback() -> int? {
   optional_call_rethrows(() -> int { throw "boom" })
 }
+
+function test_optional_call_caught_returns_null() -> int? {
+  optional_call_caught(() -> int { throw "boom" })
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@baml_language/crates/baml_tests/projects/function_type_throws/optional_call_throws.baml`
around lines 13 - 15, Add a companion integration test that exercises the catch
path by calling optional_call_caught with a throwing callback so the thrown
string is caught end-to-end; create a new test function (e.g.,
test_optional_call_with_caught_callback) that invokes optional_call_caught(() ->
int { throw "boom" }) and asserts the expected caught result or behavior, using
the same style as test_optional_call_with_throwing_callback to ensure the catch
branch is exercised for optional_call_caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@baml_language/crates/baml_compiler2_tir/src/inference.rs`:
- Around line 512-518: The check_throws_contract call is using a builder seeded
only with func_def.generic_params so inner lambdas lose enclosing generics and
thrown types lower to Unknown; update the code that calls
builder.check_throws_contract (the invocations using symbols builder,
check_throws_contract, lambda_body, func_def) to use a builder that includes the
enclosing generic parameter scope (i.e., merge or create a new builder seeded
with both the current func_def.generic_params and any outer generic params
visible to the lambda) before calling check_throws_contract so thrown types are
resolved with enclosing generics in scope; apply the same fix to the other
occurrence around the block that references the same symbols (the other call at
the later lines).
- Around line 169-188: The code in effective_throws currently rebuilds aliases
from only package_items via collect_type_aliases and passes that limited set
into crate::effective_throws::collect_effective_throws; replace that step so you
pass the full alias set instead of recomputing from package_items (do not call
collect_type_aliases(pkg_items) here). Locate the effective_throws function and
its call to crate::effective_throws::collect_effective_throws and change the
aliases argument to the global/full alias map you already maintain (e.g., the
module/global alias collection or the previously computed alias set on the
inference context) so function_throws_facts sees resolved aliases rather than
opaque Ty::TypeAlias. Ensure you remove the local collect_type_aliases use and
pass the existing full alias variable into collect_effective_throws.

In `@baml_language/crates/baml_compiler2_tir/src/throws_semantics.rs`:
- Around line 183-225: The shape check function
function_shape_matches_ignoring_outer_throws currently only matches raw
Ty::Function and misses cases where functions are wrapped in Ty::Optional;
update function_shape_matches_ignoring_outer_throws to first resolve aliases
(keep resolve_alias_chain) and then peel matching Ty::Optional layers on both
got_resolved and expected_resolved (unwrapping Box<Ty::Function> when both are
Optional) before the match, so that when both sides are Optional(Function{...})
you construct the two Function types with throws set to Ty::Never and call
normalize::is_subtype_of as before (preserve use of aliases and TyAttr
handling).

---

Outside diff comments:
In `@baml_language/crates/baml_compiler2_tir/src/throw_inference.rs`:
- Around line 69-79: The pre-inference alias map currently only includes local
aliases from collect_type_aliases(db, pkg_items) so exported type aliases from
dependencies (available in dep_interfaces) are missed; update the alias map to
merge in dependency-exported aliases before running
function_throws_facts/collect_direct_param_call_throws by iterating
dep_interfaces (the (Name, &crate::package_interface::PackageInterface) pairs
returned by package_dependencies + package_interface::package_interface) and
pulling each dependency's exported type aliases into the same aliases structure
so callback parameter types that reference exported dependency aliases resolve
correctly during throw inference.

---

Nitpick comments:
In
`@baml_language/crates/baml_tests/projects/function_type_throws/fn_decl_throws.baml`:
- Around line 15-24: The test currently calls declared_may_fail(1) with a
hard-coded constant which weakens rethrow/effect assertions; change caller and
safe_caller to accept an input parameter and forward that parameter to
declared_may_fail instead (update function signatures of caller and safe_caller
and the call sites inside them to use the parameter), keeping the catch block in
safe_caller intact; this makes the fixture use a variable input to
declared_may_fail rather than the constant 1 and preserves the intended
behaviors of declared_may_fail, caller, and safe_caller.

In
`@baml_language/crates/baml_tests/projects/function_type_throws/optional_call_throws.baml`:
- Around line 13-15: Add a companion integration test that exercises the catch
path by calling optional_call_caught with a throwing callback so the thrown
string is caught end-to-end; create a new test function (e.g.,
test_optional_call_with_caught_callback) that invokes optional_call_caught(() ->
int { throw "boom" }) and asserts the expected caught result or behavior, using
the same style as test_optional_call_with_throwing_callback to ensure the catch
branch is exercised for optional_call_caught.
🪄 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: af209f89-376b-4641-ba36-9b3981ad7f5f

📥 Commits

Reviewing files that changed from the base of the PR and between 4282b74 and bab3306.

⛔ Files ignored due to path filters (32)
  • baml_language/crates/baml_tests/snapshots/catch_return_type_mismatch/baml_tests__catch_return_type_mismatch__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__array_map_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__catch_absorbs_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__compose_hof.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__fn_decl_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__fn_type_alias_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__hof_rethrows.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__lambda_throws_violation.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__optional_call_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__returned_closures.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__01_lexer__stored_callback_enforcement.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__array_map_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__catch_absorbs_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__compose_hof.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__fn_decl_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__fn_type_alias_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__hof_rethrows.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__lambda_throws_violation.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__optional_call_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__returned_closures.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__02_parser__stored_callback_enforcement.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__array_map_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__catch_absorbs_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__fn_decl_throws.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__hof_rethrows.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__lambda_throws_violation.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/function_type_throws/baml_tests__function_type_throws__10_formatter__optional_call_throws.snap is excluded by !**/*.snap
📒 Files selected for processing (21)
  • baml_language/crates/baml_compiler2_ast/src/disambiguate.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/effective_throws.rs
  • baml_language/crates/baml_compiler2_tir/src/inference.rs
  • baml_language/crates/baml_compiler2_tir/src/lib.rs
  • baml_language/crates/baml_compiler2_tir/src/throw_inference.rs
  • baml_language/crates/baml_compiler2_tir/src/throws_semantics.rs
  • baml_language/crates/baml_lsp2_actions/src/utils.rs
  • baml_language/crates/baml_tests/projects/function_type_throws/array_map_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/catch_absorbs_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/compose_hof.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/fn_decl_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/fn_type_alias_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/hof_rethrows.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/lambda_throws_violation.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/optional_call_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/returned_closures.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/stored_callback_enforcement.baml
  • baml_language/crates/baml_tests/src/compiler2_tir/mod.rs
  • baml_language/crates/baml_type/src/lib.rs
✅ Files skipped from review due to trivial changes (4)
  • baml_language/crates/baml_compiler2_tir/src/lib.rs
  • baml_language/crates/baml_tests/projects/function_type_throws/returned_closures.baml
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_tests/projects/function_type_throws/fn_type_alias_throws.baml
🚧 Files skipped from review as they are similar to previous changes (8)
  • baml_language/crates/baml_tests/projects/function_type_throws/array_map_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/lambda_throws_violation.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/catch_absorbs_throws.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/compose_hof.baml
  • baml_language/crates/baml_tests/projects/function_type_throws/hof_rethrows.baml
  • baml_language/crates/baml_tests/src/compiler2_tir/mod.rs
  • baml_language/crates/baml_tests/projects/function_type_throws/stored_callback_enforcement.baml
  • baml_language/crates/baml_compiler2_tir/src/builder.rs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
baml_language/crates/baml_compiler2_tir/src/throws_semantics.rs (1)

266-291: Broaden this regression and run the library test suite.

This pins the raw Optional(Function) case, but the helper also depends on alias expansion. Please add a Ty::TypeAlias -> Optional(Function) variant here too, and run cargo test --lib before merge since the PR validation only lists targeted baml_tests runs.

As per coding guidelines, "Prefer writing Rust unit tests over integration tests where possible" and "Always run cargo test --lib if you changed any Rust code".

baml_language/crates/baml_compiler2_tir/src/inference.rs (2)

77-78: Consider sharing the alias map instead of cloning it into every cached scope.

infer_scope_types is Salsa-cached per scope, but this alias set is effectively file/package-wide and now gets cloned into both TypeInferenceBuilder and every ScopeInference. On files with many lambda scopes, that duplicates the same dependency-export map in each cached result and makes salsa::Update compare more data than necessary. An Arc<HashMap<...>> or a dedicated alias query would keep the new behavior without the per-scope copy cost.

Also applies to: 303-305, 682-688

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_compiler2_tir/src/inference.rs` around lines 77 -
78, The aliases HashMap stored and cloned into every cached scope (the aliases
field of ScopeInference and the data passed into TypeInferenceBuilder used by
infer_scope_types) should be shared instead of deep-copied: change the type from
HashMap<crate::ty::QualifiedTypeName, Ty> to
Arc<HashMap<crate::ty::QualifiedTypeName, Ty>> (or return an Arc from a new
dedicated query) and update all construction sites (where ScopeInference and
TypeInferenceBuilder are created and where infer_scope_types assembles aliases)
to clone the Arc rather than the map contents so Salsa comparisons and per-scope
memory use only copy the Arc pointer.

251-262: Finish extracting lambda generic-scope assembly.

You already have extend_generic_params_with_enclosing_lambdas, but the full merge order still lives in both lambda branches. Since parameter annotation lowering and check_throws_contract both rely on that exact visible generic set, folding the remaining class/function/current-lambda pieces into one helper would make this path much harder to regress.

Also applies to: 503-530, 585-596

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/baml_compiler2_tir/src/inference.rs` around lines 251 -
262, The current helper extend_generic_params_with_enclosing_lambdas only folds
generic params from enclosing lambdas; finish extracting and centralize the full
visible-generic assembly (including class, function, and current-lambda scopes)
into a single helper used by parameter-annotation lowering and
check_throws_contract to ensure consistent merge order; implement a new or
extended helper (reuse/extend extend_generic_params_with_enclosing_lambdas and
find_lambda_by_span) that takes generic_params: &mut Vec<Name>, body: &ExprBody,
source_map: &AstSourceMap and an enclosing_lambda_spans slice and appends
generics from enclosing lambdas, the current lambda, enclosing function/class
scopes in the correct merge order, then replace the duplicated merge logic at
the other sites (the blocks around the existing calls at ~lines 503-530 and
585-596) to call this single helper so both parameter annotation lowering and
check_throws_contract observe the exact same visible generic set.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@baml_language/crates/baml_compiler2_tir/src/inference.rs`:
- Around line 77-78: The aliases HashMap stored and cloned into every cached
scope (the aliases field of ScopeInference and the data passed into
TypeInferenceBuilder used by infer_scope_types) should be shared instead of
deep-copied: change the type from HashMap<crate::ty::QualifiedTypeName, Ty> to
Arc<HashMap<crate::ty::QualifiedTypeName, Ty>> (or return an Arc from a new
dedicated query) and update all construction sites (where ScopeInference and
TypeInferenceBuilder are created and where infer_scope_types assembles aliases)
to clone the Arc rather than the map contents so Salsa comparisons and per-scope
memory use only copy the Arc pointer.
- Around line 251-262: The current helper
extend_generic_params_with_enclosing_lambdas only folds generic params from
enclosing lambdas; finish extracting and centralize the full visible-generic
assembly (including class, function, and current-lambda scopes) into a single
helper used by parameter-annotation lowering and check_throws_contract to ensure
consistent merge order; implement a new or extended helper (reuse/extend
extend_generic_params_with_enclosing_lambdas and find_lambda_by_span) that takes
generic_params: &mut Vec<Name>, body: &ExprBody, source_map: &AstSourceMap and
an enclosing_lambda_spans slice and appends generics from enclosing lambdas, the
current lambda, enclosing function/class scopes in the correct merge order, then
replace the duplicated merge logic at the other sites (the blocks around the
existing calls at ~lines 503-530 and 585-596) to call this single helper so both
parameter annotation lowering and check_throws_contract observe the exact same
visible generic set.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e70b0143-2481-43ff-a505-3f31d76fb053

📥 Commits

Reviewing files that changed from the base of the PR and between bab3306 and e45b28d.

📒 Files selected for processing (2)
  • baml_language/crates/baml_compiler2_tir/src/inference.rs
  • baml_language/crates/baml_compiler2_tir/src/throws_semantics.rs

LSP tests and bytecode snapshots expected stale throws contract
violation diagnostics and old optional-function-type formatting
that changed with the typed rethrows for higher-order functions work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_variant_violation.baml (1)

19-19: Align top-of-file comments with the new no-diagnostics expectation.

Line 19 is correct now, but Line 1 and Line 2 still describe this as a mismatch/unsupported case. Updating those comments will avoid confusion for future test maintenance.

♻️ Suggested comment update
-// throws enum type, but body throws variants — contract mismatch
-// NOTE: enum variants in throws type position are not yet supported.
+// throws enum type, body throws enum variants
+// bare enum throws semantically covers thrown enum variants.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_variant_violation.baml`
at line 19, Top-of-file comment lines 1 and 2 still indicate a
mismatch/unsupported diagnostics expectation but the test now expects no
diagnostics (as shown by the marker at line 19); update those initial comments
to reflect the new expectation (e.g., replace the old mismatch/unsupported
comment text with a no-diagnostics expectation) so the file’s header matches the
actual test marker and avoids future confusion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_variant_violation.baml`:
- Line 19: Top-of-file comment lines 1 and 2 still indicate a
mismatch/unsupported diagnostics expectation but the test now expects no
diagnostics (as shown by the marker at line 19); update those initial comments
to reflect the new expectation (e.g., replace the old mismatch/unsupported
comment text with a no-diagnostics expectation) so the file’s header matches the
actual test marker and avoids future confusion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 83621e15-227e-48ba-a0ae-ffddc815235b

📥 Commits

Reviewing files that changed from the base of the PR and between 32fac73 and be22d2d.

⛔ Files ignored due to path filters (8)
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__testing_std__/baml_tests____testing_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__testing_std__/baml_tests____testing_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/lambda_basic/baml_tests__lambda_basic__04_5_mir.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 (8)
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/hover/function_throws.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_caller_sees_contract.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_caller_variant_contract.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_exact_match.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_extraneous.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_variant_precise.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_variant_violation.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_mixed.baml
✅ Files skipped from review due to trivial changes (6)
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_extraneous.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_variant_precise.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_exact_match.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_mixed.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_caller_sees_contract.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/hover/function_throws.baml

@rossirpaulo rossirpaulo added this pull request to the merge queue Apr 8, 2026
@rossirpaulo rossirpaulo removed this pull request from the merge queue due to a manual request Apr 8, 2026
rossirpaulo and others added 2 commits April 9, 2026 00:05
Consolidate signature lowering (params, ret, throws, effect vars) into a
shared `callable_boundary` module used by builder, inference, and
throw_inference — eliminating duplicated logic.  Only widen outward throws
for callbacks that are actually invoked in the body, add diagnostic
formatting that renders `__throws_` type vars as human-readable messages,
and extend test coverage for under-declared rethrows, unused callbacks,
stored callback enforcement, and generic class methods.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enforce the A+B contract: callback parameters with implicit effect
polymorphism that are never directly invoked now produce an actionable
error. This closes a type soundness hole where throws were silently
dropped.

- Add UnusedCallbackEffectVar error variant in infer_context.rs
- Emit diagnostic in inference.rs by capturing direct_callback_effect_vars
- Add LSP error code mapping in check.rs
- Update test files with ERROR comments for expected diagnostics
- Fix stdlib functions with explicit throws annotations
Filter synthetic_display_vars in the TIR display to only show effect
vars whose source callback params are directly invoked. This ensures
the displayed throws type matches runtime behavior.

- Make directly_invoked_callback_params public in callable_boundary.rs
- Filter synthetic display vars in Branch 2 of throws display logic
- Update TIR snapshots to reflect corrected display
…ollections

Add targeted regression tests for two scenarios supported by the
machinery but not directly exercised:
- Generic stored callback fields (Handler<E> pattern)
- Heterogeneous stored callback collection widening (Task<E>[])

Note: These tests currently show throws never because generic type
parameters in stored callback fields are not yet instantiated during
throws inference. The tests serve as regression coverage for when this
is improved.
Phase 1: StructuralTy::Class and StructuralTy::Enum now carry type_args,
enabling invariant subtype comparison for instantiated nominals (e.g.
Handler<string> vs Handler<int> are distinct). Shared instantiate_alias
helper funnels all six alias expansion sites. Generic inference helpers
(contains_typevar, infer_bindings, erase_unresolved_typevars) recurse
into nominal type_args.

Phase 2: Cross-package class field and method resolution routed through
PackageResolutionContext with generic substitution applied inside the
methods. Builder delegates to res_ctx instead of bypassing PackageInterface.
Fixed cross-namespace struct literal resolution fallback.

New test fixtures for generic subtype rejection and generic function
inference over nominal wrappers.
Add collect_member_field_call_throws to the pre-inference throw pipeline
so that throws from typed member calls (h.run(), self.run(), task.run())
propagate to callers. Uses PackageResolutionContext for member lookup
with generic substitution applied.

test_string_handler now correctly infers throws string,
test_int_handler infers throws int, and test_run_mixed_tasks infers
throws int | string via transitive propagation.
Update display_type_expr to render type_args recursively for
TypeExpr::Path (e.g. Handler<string> instead of Handler).

Include generic_params in hover tooltips for class and function
definitions so class Handler<E> and function unwrap<T> show their
type parameters.

Add LSP hover test infrastructure (CursorTest::type_info) and 11 tests
covering generic class/function definitions, usage-site resolution,
nested generics, optional/array generic params, and markdown rendering.
Twelve 04_tir snapshots updated with trivially larger serialization
from the new Vec<StructuralTy> type_args on Class/Enum variants.
MIR snapshots (04_5_mir) are unchanged — no scope leak.
Add throws field to TypeInfo::Function populated from either inferred
transitive throw sets (via function_throw_sets TIR query) or the
declared throws clause from the function signature.

Hover now shows e.g. `function use_handler(h: Handler<string>) -> null throws string`
for functions with inferred or declared throws. Functions that throw
never omit the throws clause entirely.

Add 4 new LSP hover tests covering: no-throws omission, declared throws,
inferred throws from member calls, and union throws.
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