Skip to content

feat(interfaces): destructure interface fields in match (BEP-044) - #3632

Merged
aaronvg merged 10 commits into
canaryfrom
aaron/interfaces2
Jun 2, 2026
Merged

aaronvg merged 10 commits into
canaryfrom
aaron/interfaces2

Conversation

@aaronvg

@aaronvg aaronvg commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

What

Adds interface-field destructuring in match patterns. An interface head binds the interface's declared fields across every implementor:

function describe(a: Animal) -> string {
  match (a) {
    Animal { name } => "animal: " + name   // binds `name` for any implementor
  }
}

name resolves through each implementor's field view, so it works whether the field was auto-linked (Dog { name }) or as-aliased (Cat { name as nickname }). Because every implementor necessarily provides the interface's declared fields, the pattern matches them all — so Animal { name } is exhaustive on its own (no _ needed).

Previously Animal { name } was mis-lowered as a construction expression (unresolved name: Animal); only concrete-class destructure (Dog { name }) worked.

How

  • TIR (baml_compiler2_tir/src/builder.rs): resolve_class_pattern_type accepts interface heads; lower_class_pat has an interface branch that binds each field's type via resolve_interface_member and produces a wildcard-cover DPat.
  • MIR (baml_compiler2_mir/src/lower.rs): project_class_pattern_field routes interface heads to a new project_interface_pattern_field, reusing the existing interface field-view dispatch (try_lower_interface_field_access). The MIR Ty has no interface variant, so the route keys off the raw Tir2Ty::Interface.

Tests

  • New: match_destructures_interface_fields_directly (interface head, auto-linked + aliased implementors) and match_destructures_concrete_implementor_fields.
  • Refreshed the BEP-044 regression-suite comments — all pass now. The two interface-method-as-value cases (fuzz_bug01/02) remain #[ignore]d (genuinely unimplemented).
  • Interfaces suite: 339 passed / 2 ignored; full baml_tests (30 binaries) and cargo check --workspace clean.

The matching BEP-044 spec update (match syntax, this feature, and other implementation-vs-draft corrections) was pushed separately to beps.boundaryml.com.

Also included

In-flight Python SDK / bridge / codegen fixes that were already present on the working branch (bridge_cffi, bridge_python, codegen_python, harness_setup, baml_cli/generate.rs, .pyi stubs). Not authored as part of the interface work; bundled per request.

🤖 Generated with Claude Code


Note

High Risk
Changes span TIR/MIR pattern lowering and exhaustiveness (easy to get match soundness wrong) plus the Python runtime initialization path that all generated SDKs use after baml generate.

Overview
Implements BEP-044 interface destructuring in match (Animal { name } => …): TIR resolves interface pattern heads and lowers DPat::interface with field-view types; exhaustiveness gains Ctor::Interface and matrix specialization that maps interface field slots onto implementing class fields; MIR projects bound fields via project_interface_pattern_field / existing interface field dispatch.

Python codegen/runtime now embeds borsh-serialized bytecode instead of inlined .baml source: baml generate compiles and calls to_source_code_with_bytecode; bex_project::new_from_bytecode, CFFI/Python initialize_runtime_from_bytecode, and generated _inlinedbaml.BYTECODE wiring. CI size-gate baselines for baml-cli are bumped slightly.

Large interface test suite additions (compile + VM) for destructure exhaustiveness, mixed concrete/interface arms, generics, and updated regression comments (most fuzz/wf3 cases now pass; method-as-value tests still ignored).

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

Summary by CodeRabbit

  • New Features

    • Match expressions now support destructuring of interface-typed values.
    • SDKs can initialize the runtime from precompiled BAML bytecode (new runtime entrypoint and corresponding Python initializer).
  • Tests

    • Added end-to-end tests covering interface-field destructuring, exhaustive matching, and related runtime behaviors.
  • Chores

    • Test harness and workspace updated for bytecode support (borsh); CI size-gate baselines updated.

@vercel

vercel Bot commented Jun 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Jun 2, 2026 3:38am
promptfiddle Ready Ready Preview, Comment Jun 2, 2026 3:38am
promptfiddle2 Ready Ready Preview, Comment Jun 2, 2026 3:38am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

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

This PR implements interface-head destructuring across TIR, exhaustiveness, and MIR lowering, and switches Python SDK code generation to embed borsh-serialized BAML bytecode with runtime initialization paths and corresponding test-harness and tests.

Changes

Interface Destructuring and Bytecode Integration

Layer / File(s) Summary
CLI: compile to bytecode and route Python codegen
baml_language/crates/baml_cli/src/generate.rs
Compiles project to a Program, serializes it via borsh::to_vec into baml_bytecode, and passes bytecode to Python emitter entrypoint instead of inlined source files.
Python emitter: RuntimePayload and bytecode rendering
baml_language/sdks/python/rust/codegen_python/src/lib.rs
Adds RuntimePayload enum and to_source_code_with_bytecode; __init__.py emission conditionally calls BamlRuntime.initialize_runtime_from_bytecode(_inlinedbaml.BYTECODE); _inlinedbaml.py can emit BYTECODE: bytes using py_bytes; unit test added.
Runtime deserialization and CFFI bridge init
baml_language/crates/bex_project/src/lib.rs, baml_language/crates/bridge_cffi/src/lib.rs, baml_language/crates/bex_project/Cargo.toml
Adds borsh dep, bex_project::new_from_bytecode to deserialize Program and build engine, extracts runtime_event_sink() and replace_runtime(), and adds initialize_runtime_from_bytecode to construct and install runtime from bytecode.
Python bridge & stubs: PyO3 method and typing stubs
baml_language/sdks/python/rust/bridge_python/src/runtime.rs, .../__init__.pyi, .../baml_py.pyi
Exposes BamlRuntime.initialize_runtime_from_bytecode PyO3 staticmethod and matching .pyi declarations; delegates to CFFI and maps errors into SDK panic behavior.
Test harness: fixture bytecode and codegen switch
baml_language/sdk_tests/harness_setup/Cargo.toml, .../src/lib.rs, .../src/python_pydantic2.rs
Adds borsh dependency; LoadedFixture includes baml_bytecode: Vec<u8> populated by serializing compiled Program; Python harness calls to_source_code_with_bytecode.
TIR: resolve and lower interface-head patterns
baml_language/crates/baml_compiler2_tir/src/builder.rs
resolve_class_pattern_type and lower_class_pat accept Ty::Interface; adds interface_field_infos_ordered and PatCtx::interface_field_types; lowers interface-head destructuring to DPat::interface(...).
Exhaustiveness: interface ctor and matrix integration
baml_language/crates/baml_compiler2_tir/src/exhaustiveness.rs
Adds Ctor::Interface(Ty), DPat::interface(...), PatCtx::interface_field_types, updates equality/hash/arity/covers/ctor_sub_tys/split_ctors and witness rendering for interface ctors.
MIR lowering: interface field-access and pattern projection
baml_language/crates/baml_compiler2_mir/src/lower.rs
Simplifies try_lower_interface_field_access signature, updates call sites, adds project_interface_pattern_field, and routes interface-head pattern projections through interface field-view dispatch.
Tests & docs: interface suite updates and new runtime tests
baml_language/crates/baml_tests/tests/interfaces.rs
Module header and many inline comments updated to reflect end-to-end BEP-044 semantics; three new async Tokio runtime tests validate interface-head destructuring, field-value subpatterns, and concrete-implementor narrowing.
CI size-gate metrics
baml_language/.ci/size-gate/*
Updated recorded size metrics for baml-cli artifacts on multiple platforms.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as cli generate
  participant CodeGen as codegen_python
  participant Root as __init__.py
  participant Inlined as _inlinedbaml.py

  CLI->>CodeGen: to_source_code_with_bytecode(pool, bytecode)
  CodeGen->>Inlined: emit BYTECODE bytes literal
  CodeGen->>Root: emit BamlRuntime.initialize_runtime_from_bytecode(...)
  Root->>Inlined: import _inlinedbaml and pass BYTECODE to initialize_runtime_from_bytecode
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • BoundaryML/baml#3417: Related compiler2 pattern-lowering/type-system work that this PR builds on for BEP-044 interface-destructuring support.
  • BoundaryML/baml#3555: Prior interface-v2 semantics and exhaustiveness changes that this PR extends with TIR/MIR integration.

Poem

🐰 In bytecode burrows I sew,

interfaces branch and patterns grow,
Tests hop forward, runtime wakes,
Python packages learn new takes,
A carrot-shaped commit—clear to show.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(interfaces): destructure interface fields in match (BEP-044)' clearly and specifically summarizes the main change: enabling interface field destructuring in match patterns, with BEP-044 reference for traceability.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 aaron/interfaces2

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.

@vercel
vercel Bot temporarily deployed to Preview – beps June 1, 2026 23:52 Inactive
… tests

Interface-field destructuring in `match`: an interface head
(`Animal { name } => ...`) now binds the interface's declared fields across
every implementor, dispatching through each implementor's field view — so an
auto-linked field and an `as`-aliased field both resolve correctly. The
pattern matches all implementors, so it is exhaustive on its own.

- TIR (builder.rs): accept interface heads in destructure patterns; bind
  fields via resolve_interface_member.
- MIR (lower.rs): project interface-pattern fields through the existing
  interface field-view dispatch (try_lower_interface_field_access).
- Tests (interfaces.rs): refresh BEP-044 regression-suite comments — all
  pass now; the two interface-method-as-value cases remain ignored
  (unimplemented) — and add coverage for interface and concrete-implementor
  destructuring.

Also bundles in-flight Python SDK / bridge / codegen fixes already present
on the working branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@aaronvg
aaronvg force-pushed the aaron/interfaces2 branch from c6b4687 to aac3777 Compare June 1, 2026 23:53
@vercel
vercel Bot temporarily deployed to Preview – beps June 1, 2026 23:54 Inactive
@github-actions

github-actions Bot commented Jun 1, 2026 •

Copy link
Copy Markdown

Binary size checks passed

✅ 7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
✅ baml-cli Linux 🔒 20.0 MB 8.3 MB file 19.4 MB +577.5 KB (+3.0%) OK
✅ packed-program Linux 🔒 15.0 MB 6.3 MB file 14.7 MB +345.4 KB (+2.4%) OK
✅ baml-cli macOS 🔒 15.1 MB 7.2 MB file 15.1 MB -5.1 KB (-0.0%) OK
✅ packed-program macOS 🔒 11.4 MB 5.5 MB file 11.2 MB +264.9 KB (+2.4%) OK
✅ baml-cli Windows 🔒 16.3 MB 7.4 MB file 16.3 MB -5.1 KB (-0.0%) OK
✅ packed-program Windows 🔒 12.2 MB 5.6 MB file 11.9 MB +314.7 KB (+2.7%) OK
✅ bridge_wasm WASM 13.4 MB 🔒 3.9 MB gzip 3.8 MB +8.7 KB (+0.2%) OK

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


Generated by cargo size-gate · workflow run

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
baml_language/crates/baml_tests/tests/interfaces.rs (1)

2215-2223: ⚡ Quick win

Exercise the Cat { name } branch in this test.

describe has two concrete destructure arms, but main() only feeds a Dog, so this runtime test never proves the Cat { name } pattern executes correctly.

Suggested change
         function main() -> string {
-            let a: Animal = Dog { name: "Rex", breed: "Lab" }
-            return describe(a)
+            let dog: Animal = Dog { name: "Rex", breed: "Lab" }
+            let cat: Animal = Cat { name: "Luna" }
+            return describe(dog) + " / " + describe(cat)
         }
@@
-        BexExternalValue::String("Rex the Lab".into())
+        BexExternalValue::String("Rex the Lab / Luna the cat".into())
🤖 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_tests/tests/interfaces.rs` around lines 2215 -
2223, Test only constructs a Dog currently, so the describe function's Cat {
name } arm is never executed; update the test that builds main()/output.result
to also exercise the Cat branch by either adding a second invocation of main (or
a separate test) that constructs Animal::Cat { name: "Whiskers" } and asserts
output.result.unwrap() equals BexExternalValue::String("Whiskers".into()) (or
the expected string format), or modify the existing test to run both variants
sequentially; target the function name main, the enum/type Animal, and the
pattern arms Dog and Cat so the Cat { name } branch is actually exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 12748-12777: The Ty::Interface branch currently discards the
lowered per-field dpat results from analyze_and_lower and replaces them with a
generic dpat from dpat_for_type(&class_ty, scrut_ty), which loses the specific
field subpatterns and breaks exhaustiveness/refutability checks; instead,
collect each field's PatternResult.dpat (and their bindings) from the loop,
compose them into a structural/interface dpat that preserves per-field
subpatterns (rather than calling dpat_for_type unconditionally), and return that
composed dpat in the PatternResult while still setting required_ty:
Some(class_ty.clone()) and matched_ty from
intersect_pattern_flow_types(scrut_ty, &class_ty); reference analyze_and_lower,
PatternResult, dpat_for_type, Ty::Interface, bindings, and
intersect_pattern_flow_types to locate the change.
- Around line 12753-12767: The current call to resolve_interface_member permits
methods/defaults (so patterns like Animal { speak } wrongly type-check); change
this to a field-only lookup or add an explicit check after
resolve_interface_member to reject non-field members: call the field-specific
resolver (e.g., resolve_interface_field) if available, or call
resolve_interface_member and then inspect the returned member kind (compare
against MemberKind::Field or equivalent) and if it is not a field call
report_at_pat_or_expr with TirTypeError::UnresolvedMember (using
class_ty.clone(), fp.field.clone(), pat_id, at_expr) and return the Ty::Unknown
fallback as currently done. Ensure you reference iface_name, type_args,
fp.field, pat_id, and at_expr in the error path so non-field members are
rejected at this site rather than allowed.

---

Nitpick comments:
In `@baml_language/crates/baml_tests/tests/interfaces.rs`:
- Around line 2215-2223: Test only constructs a Dog currently, so the describe
function's Cat { name } arm is never executed; update the test that builds
main()/output.result to also exercise the Cat branch by either adding a second
invocation of main (or a separate test) that constructs Animal::Cat { name:
"Whiskers" } and asserts output.result.unwrap() equals
BexExternalValue::String("Whiskers".into()) (or the expected string format), or
modify the existing test to run both variants sequentially; target the function
name main, the enum/type Animal, and the pattern arms Dog and Cat so the Cat {
name } branch is actually exercised.
🪄 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: c36f126a-b3bf-4044-808b-f97f0653c22c

📥 Commits

Reviewing files that changed from the base of the PR and between 26c4329 and aac3777.

⛔ Files ignored due to path filters (1)
  • baml_language/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • baml_language/crates/baml_cli/src/generate.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_tests/tests/interfaces.rs
  • baml_language/crates/bridge_cffi/Cargo.toml
  • baml_language/crates/bridge_cffi/src/lib.rs
  • baml_language/sdk_tests/harness_setup/Cargo.toml
  • baml_language/sdk_tests/harness_setup/src/lib.rs
  • baml_language/sdk_tests/harness_setup/src/python_pydantic2.rs
  • baml_language/sdks/python/rust/bridge_python/src/baml_core/baml_py/__init__.pyi
  • baml_language/sdks/python/rust/bridge_python/src/runtime.rs
  • baml_language/sdks/python/rust/codegen_python/src/lib.rs
  • baml_language/sdks/python/src/baml_core/baml_py.pyi

`initialize_runtime_from_bytecode` built a `bex_engine::BexEngine` from a
borsh-decoded `bex_vm_types::Program` directly, which `cargo stow` rejects:
bridge crates may depend only on `bex_project` re-exports, not on bex
internals (`bex_engine`, `bex_vm_types`).

Add `bex_project::new_from_bytecode(&[u8], SysOps, Option<EventSink>)` that owns
the borsh decode + engine construction, and re-point `bridge_cffi` at it.
`bridge_cffi` drops its `bex_engine`, `bex_vm_types`, and `borsh` deps.
`cargo stow` now passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@vercel
vercel Bot temporarily deployed to Preview – beps June 2, 2026 00:07 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps June 2, 2026 00:12 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
baml_language/crates/bex_project/src/lib.rs (1)

8-9: 💤 Low value

Module header lists only one of the two constructors.

The header says "Two public constructors:" but only documents new. Now that new_from_bytecode is a public constructor, add it here so the module doc matches the surface.

📝 Proposed doc update
 //! Two public constructors:
 //! - [`new`] — compile source files and return `Arc<dyn Bex>`.
+//! - [`new_from_bytecode`] — load a pre-packed `bex_vm_types::Program` and
+//!   return `Arc<dyn Bex>` without compiling from source.
🤖 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/bex_project/src/lib.rs` around lines 8 - 9, Module
header lists "Two public constructors" but only documents `new`; update the
module-level doc comment in lib.rs to include `new_from_bytecode` as the second
constructor and give a short description (e.g., "construct from bytecode and
return Arc<dyn Bex>") so the doc comment matches the public API (`new` and
`new_from_bytecode`).
🤖 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.

Nitpick comments:
In `@baml_language/crates/bex_project/src/lib.rs`:
- Around line 8-9: Module header lists "Two public constructors" but only
documents `new`; update the module-level doc comment in lib.rs to include
`new_from_bytecode` as the second constructor and give a short description
(e.g., "construct from bytecode and return Arc<dyn Bex>") so the doc comment
matches the public API (`new` and `new_from_bytecode`).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bed9c1c7-ad5d-42d9-a1c9-ee312f89c926

📥 Commits

Reviewing files that changed from the base of the PR and between aac3777 and b94ba73.

⛔ Files ignored due to path filters (1)
  • baml_language/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • baml_language/crates/bex_project/Cargo.toml
  • baml_language/crates/bex_project/src/lib.rs
  • baml_language/crates/bridge_cffi/src/lib.rs
✅ Files skipped from review due to trivial changes (1)
  • baml_language/crates/bex_project/Cargo.toml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
baml_language/crates/baml_compiler2_tir/src/builder.rs (1)

12818-12833: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require type args on generic interface destructures.

This branch now accepts interface heads, but it does not mirror the existing generic-class check. class_pattern_missing_generic_args(...) only recognizes Ty::Class, so a pattern like Container { item } against Container<int> falls through here with type_args.is_empty(). The subsequent interface_field_infos_ordered(...) call then lowers item against the interface's bare type parameter (T) instead of the concrete instantiation (int), and union dispatch/exhaustiveness also lose the concrete interface member. Please reject missing type args for generic interfaces before lowering, the same way class destructures already do.

🛠️ Suggested direction
         let class_ty =
             self.resolve_class_pattern_type(class, generic_args, Some((pat_id, at_expr)));

+        if self.class_pattern_missing_generic_args(&class_ty, generic_args) {
+            let err = TirTypeError::GenericClassDestructureRequiresTypeArgs {
+                class_name: class.last().cloned().unwrap_or_else(|| Name::new("_")),
+            };
+            self.context.report_simple(err, at_expr);
+        }
+
         if let Ty::Interface(iface_name, type_args, _) = &class_ty {

…and extend class_pattern_missing_generic_args to treat Ty::Interface the same way it already treats Ty::Class.

🤖 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 12818 -
12833, The interface-destructure branch accepts Ty::Interface but doesn't reject
missing generic args, causing lowering (interface_field_infos_ordered) to use
bare type parameters; update the pre-lowering check to reject missing type args
for generic interfaces the same way as classes: extend the
function/class_pattern_missing_generic_args to treat Ty::Interface like
Ty::Class (or add an explicit check after resolve_class_pattern_type when
class_ty is Ty::Interface) and return an error if type_args.is_empty(), ensuring
patterns like Container { item } against Container<int> are rejected before
calling interface_field_infos_ordered.
🧹 Nitpick comments (2)
baml_language/crates/baml_compiler2_tir/src/exhaustiveness.rs (1)

1305-1320: 💤 Low value

Consider adding unit tests for interface pattern exhaustiveness.

The exhaustiveness module has comprehensive unit tests for classes, slices, unions, and other pattern types, but no tests specifically exercise Ctor::Interface behavior. While integration tests in interfaces.rs cover end-to-end behavior, unit tests here would verify:

  • Ctor::Interface equality/hash semantics
  • Interface arity calculation via interface_field_types
  • The special-case logic in split_ctors (lines 1126-1132) that treats interface patterns as exhaustive

A TestingCtx extension with interface_field_types implementation would enable testing interface-specific exhaustiveness scenarios. As per coding guidelines, unit tests are preferred 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/exhaustiveness.rs` around lines
1305 - 1320, Add unit tests exercising Ctor::Interface behavior in this module:
extend TestingCtx with an implementation of interface_field_types so tests can
construct interface arities, then add tests that assert Ctor::Interface
equality/hash semantics, verify arity via interface_field_types, and cover the
special-case logic in split_ctors that treats interface patterns as exhaustive
(the branch around split_ctors lines handling interfaces). Use existing test
helpers/constructors in the module to build interface patterns and assert
expected exhaustiveness outcomes.
baml_language/crates/baml_tests/tests/interfaces.rs (1)

2185-2213: ⚡ Quick win

Cover the active: true / aliased path too.

Line 2204 only pins the false arm against an auto-linked implementor. A regression in Switch { active: true }, or in literal subpattern projection when active is backed by an as alias instead of a concrete field, would still pass this test. Please add a second runtime case for the true branch, ideally through an aliased implementor.

🤖 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_tests/tests/interfaces.rs` around lines 2185 -
2213, The test match_interface_destructure_respects_field_subpatterns only
exercises the "off" path; add a second runtime case that constructs an
implementor whose active field is true via an aliased field (use a class that
implements Switch and exposes active through an `as` alias), call describe(s)
with that instance and assert the result is BexExternalValue::String("on").
Update the baml_test! input to include the aliased implementor definition and a
main/second invocation (or a separate test block) that returns/describes an
instance with active: true so the Switch { active: true } arm is exercised at
runtime.
🤖 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_tir/src/builder.rs`:
- Around line 12818-12833: The interface-destructure branch accepts
Ty::Interface but doesn't reject missing generic args, causing lowering
(interface_field_infos_ordered) to use bare type parameters; update the
pre-lowering check to reject missing type args for generic interfaces the same
way as classes: extend the function/class_pattern_missing_generic_args to treat
Ty::Interface like Ty::Class (or add an explicit check after
resolve_class_pattern_type when class_ty is Ty::Interface) and return an error
if type_args.is_empty(), ensuring patterns like Container { item } against
Container<int> are rejected before calling interface_field_infos_ordered.

---

Nitpick comments:
In `@baml_language/crates/baml_compiler2_tir/src/exhaustiveness.rs`:
- Around line 1305-1320: Add unit tests exercising Ctor::Interface behavior in
this module: extend TestingCtx with an implementation of interface_field_types
so tests can construct interface arities, then add tests that assert
Ctor::Interface equality/hash semantics, verify arity via interface_field_types,
and cover the special-case logic in split_ctors that treats interface patterns
as exhaustive (the branch around split_ctors lines handling interfaces). Use
existing test helpers/constructors in the module to build interface patterns and
assert expected exhaustiveness outcomes.

In `@baml_language/crates/baml_tests/tests/interfaces.rs`:
- Around line 2185-2213: The test
match_interface_destructure_respects_field_subpatterns only exercises the "off"
path; add a second runtime case that constructs an implementor whose active
field is true via an aliased field (use a class that implements Switch and
exposes active through an `as` alias), call describe(s) with that instance and
assert the result is BexExternalValue::String("on"). Update the baml_test! input
to include the aliased implementor definition and a main/second invocation (or a
separate test block) that returns/describes an instance with active: true so the
Switch { active: true } arm is exercised at runtime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e3ae5bbe-492d-4c3a-b2b8-d1e880dd5a67

📥 Commits

Reviewing files that changed from the base of the PR and between b94ba73 and a009951.

📒 Files selected for processing (3)
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/exhaustiveness.rs
  • baml_language/crates/baml_tests/tests/interfaces.rs

@aaronvg
aaronvg enabled auto-merge June 2, 2026 00:20
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 2, 2026 00:30 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 2, 2026 00:30 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps June 2, 2026 00:40 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 2, 2026 00:58 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 2, 2026 00:58 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps June 2, 2026 01:00 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps June 2, 2026 01:04 Inactive
@aaronvg
aaronvg disabled auto-merge June 2, 2026 01:06
@vercel
vercel Bot temporarily deployed to Preview – beps June 2, 2026 01:14 Inactive

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 379f7e4. Configure here.

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 2, 2026 01:30 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 2, 2026 01:31 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps June 2, 2026 01:42 Inactive
@aaronvg
aaronvg enabled auto-merge June 2, 2026 01:50
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 2, 2026 01:59 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 2, 2026 01:59 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps June 2, 2026 03:21 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 2, 2026 03:38 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 2, 2026 03:38 Inactive
@aaronvg
aaronvg added this pull request to the merge queue Jun 2, 2026
Merged via the queue into canary with commit 1742580 Jun 2, 2026
75 of 81 checks passed
@aaronvg
aaronvg deleted the aaron/interfaces2 branch June 2, 2026 06:30

This branch was previously deployed

3 inactive deployments
Preview – promptfiddle — e5795786 Deployed Jun 2, 2026 by vercel[bot]
Preview – promptfiddle2 — e5795786 Deployed Jun 2, 2026 by vercel[bot]
Preview – beps — e5795786 Deployed Jun 2, 2026 by vercel[bot]
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