Skip to content

Fix interface Self bugs - #3647

Merged
2kai2kai2 merged 13 commits into
canaryfrom
kai/comparison
Jun 3, 2026
Merged

2kai2kai2 merged 13 commits into
canaryfrom
kai/comparison

Conversation

@2kai2kai2

@2kai2kai2 2kai2kai2 commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor
  • Self should behave like a pinned type variable, not an dyn-interface-type. Makes this (correctly) produce a compiler error:
interface Equatable {
    function eq(self, other: Self) -> bool
}
function cmp<S extends Equatable, U extends Equatable>(x: S, y: U) -> bool {
    return x.eq(y)
}
  • Limits interface implementations to only concrete types. You cannot do implement Foo for int | string. This prohibition applies to all abstract types: unions, optionals, interfaces, unknown
  • Fixes to bounds across typevars

Summary by CodeRabbit

  • New Features
    • BEP-044: Self-pinned support for interface default methods; interface-method values now dispatch to concrete implementors at runtime.
  • Diagnostics
    • New validation rejecting non-concrete implements ... for ... targets (error E0138).
  • Tests
    • Added compile-time and runtime tests for Self-pinned default methods, associated-type behavior, generic-bound soundness, and implements ... for ... constraints.

@vercel

vercel Bot commented Jun 2, 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 3, 2026 10:45pm
promptfiddle Ready Ready Preview, Comment Jun 3, 2026 10:45pm
promptfiddle2 Ready Ready Preview, Comment Jun 3, 2026 10:45pm

Request Review

@2kai2kai2
2kai2kai2 enabled auto-merge June 2, 2026 23:01
@coderabbitai

coderabbitai Bot commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a7b026a2-254a-436c-b1c6-7a7c5387bff9

📥 Commits

Reviewing files that changed from the base of the PR and between 19e83b7 and 74cf8e0.

📒 Files selected for processing (8)
  • 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/generics.rs
  • baml_language/crates/baml_compiler2_tir/src/inference.rs
  • baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_tests/tests/interfaces.rs
  • baml_language/crates/baml_tests/tests/interfaces_associated_types.rs
💤 Files with no reviewable changes (3)
  • baml_language/crates/baml_tests/tests/interfaces_associated_types.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_tests/tests/interfaces.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • baml_language/crates/baml_compiler2_tir/src/generics.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs

📝 Walkthrough

Walkthrough

Implements BEP-044 Self-pinned interface methods across TIR generics/inference, member resolution/call checking, out-of-body signature lowering, MIR bound-method lowering, diagnostics, and tests.

Changes

BEP-044 Self-Pinned Interface Methods

Layer / File(s) Summary
Foundation: generics, inference, LSP, diagnostics
baml_language/crates/baml_compiler2_tir/src/generics.rs, baml_language/crates/baml_compiler2_tir/src/inference.rs, baml_language/crates/baml_lsp2_actions/src/check.rs, baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs
Adds contains_typevar_where and infer_bindings_rigid_self; seeds interface-scoped Self bounds during function inference; handles out-of-body implements for signature lowering and concreteness checks; adds ImplTargetNotConcrete (E0138).
Member resolution & call-check builder
baml_language/crates/baml_compiler2_tir/src/builder.rs
Introduces SelfReceiver, threads rigid_self_var through call checking, records self_pinned_rigid_var per callee, refactors resolve_interface_member to centralize Self substitution and object-safety probes, and refines subtype/reflexivity and defer-vs-check logic for rigid typevars.
MIR lowering for bound methods & tests
baml_language/crates/baml_compiler2_mir/src/lower.rs, baml_language/crates/baml_tests/tests/interfaces.rs, baml_language/crates/baml_tests/tests/interfaces_associated_types.rs
MIR seeds Self generic bounds for interface default methods; lowers method-as-value on interface-typed receivers by resolving implementor candidates and emitting runtime guard switches to produce Rvalue::MakeBoundMethod; adds BEP-044 runtime and compile-fail tests and associated-type tests.

Sequence Diagram(s)

sequenceDiagram
  participant Source as Source/Parser
  participant TIR as TIR (generics/inference)
  participant Builder as CallChecker/MemberResolver
  participant LSP as SignatureChecker
  participant MIR as MIR Lowering
  participant Runtime as Runtime Guard
  Source->>TIR: add rigid Self handling, seed interface Self bounds
  TIR->>Builder: propagate rigid_self_var, resolve members with SelfReceiver
  Builder->>LSP: validate out-of-body impl `for` targets
  Builder->>MIR: record pinned Self & bound-method candidates
  MIR->>Runtime: emit guard-switch -> MakeBoundMethod
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • BoundaryML/baml#3638: Related MIR/TIR interface-method dispatch and candidate switching refinements overlapping runtime-guarded dispatch.
  • BoundaryML/baml#3555: Earlier interface-v2 lowering/dispatch work that this change builds upon.

Poem

A rabbit bounds through interfaces new,
Where Self stays pinned and steadfast too,
Methods bind firm at runtime's call,
Types hold steady — no slipping at all. 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix interface Self bugs' clearly and concisely summarizes the main change—resolving issues with how Self type variables are handled in interface default methods, which is the primary focus across all modified files.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kai/comparison

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 – promptfiddle2 June 2, 2026 23:04 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.

Actionable comments posted: 2

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)

2519-2527: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let Phase 0 rebind the pinned Self.

This reverse-inference step still uses infer_bindings, so a contextual expected type can bind the rigid Self before argument checking runs. For a bound zero-arg method like clone() -> Self, let y: U = x.clone() can infer Self -> U, suppress the later result mismatch, and accept an invalid projection.

Suggested fix
-                            crate::generics::infer_bindings(ret, phase0_expected, &mut bindings);
+                            crate::generics::infer_bindings_rigid_self(
+                                ret,
+                                phase0_expected,
+                                &mut bindings,
+                                rigid_self_var.as_ref(),
+                            );
🤖 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 2519 -
2527, Phase 0 reverse-inference is allowed to rebind a pinned Self via the call
to crate::generics::infer_bindings(ret, phase0_expected, &mut bindings); avoid
that by skipping infer_bindings when the return type `ret` (or the current
bindings) contains the pinned/rigid Self typevar: before calling infer_bindings
in the block that checks phase0_expected and
crate::generics::contains_typevar(ret), add a guard that detects the pinned Self
(e.g., via an existing helper or a new
crate::generics::contains_pinned_self(ret) check or by inspecting bindings for
Self) and only call infer_bindings when Self is not pinned so `Self` cannot be
rebound during Phase 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@baml_language/crates/baml_compiler2_mir/src/lower.rs`:
- Around line 3691-3737: The dispatch misses captured receiver roots because the
recv_root_local lookup only checks self.locals; change the lookup for
recv_root_local to also consult captured roots (e.g. use
self.locals.get(&segments[0]).or_else(|| self.captured_roots.get(&segments[0])))
so captured `x` in closures will be treated the same as local roots, then
proceed to use lower_path_receiver_to_local,
interface_dispatch_target_for_tir_ty, and emit_bound_method_candidate_switch as
before; ensure the rest of the branch logic (receiver_segments, recv_seg_idx,
path_segment_types lookup) is unchanged so bound-method-value lowering works for
captured receivers.

In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 2667-2673: The defers_typevar predicate currently only checks
self.generic_param_bounds so outer caller generics (e.g. a bare T) are treated
as deferable; update the closure used in contains_typevar_where to also consult
the caller generic list (generic_params) and treat any name present in
generic_params as non-deferable/rigid. Concretely, in the closure for
defers_typevar (the one referencing rigid_self_var, self.generic_param_bounds
and generic_params) add a check that if name is in generic_params it sets rigid
= true (or otherwise prevents deferral), so only names that are in
generic_param_bounds but not in generic_params remain deferable.

---

Outside diff comments:
In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 2519-2527: Phase 0 reverse-inference is allowed to rebind a pinned
Self via the call to crate::generics::infer_bindings(ret, phase0_expected, &mut
bindings); avoid that by skipping infer_bindings when the return type `ret` (or
the current bindings) contains the pinned/rigid Self typevar: before calling
infer_bindings in the block that checks phase0_expected and
crate::generics::contains_typevar(ret), add a guard that detects the pinned Self
(e.g., via an existing helper or a new
crate::generics::contains_pinned_self(ret) check or by inspecting bindings for
Self) and only call infer_bindings when Self is not pinned so `Self` cannot be
rebound during Phase 0.
🪄 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: 3e9fcb5c-76bf-4431-a8b8-07079fde81bd

📥 Commits

Reviewing files that changed from the base of the PR and between e41565f and fbc96d4.

📒 Files selected for processing (7)
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/generics.rs
  • baml_language/crates/baml_compiler2_tir/src/inference.rs
  • baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_tests/tests/interfaces.rs

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 2, 2026 23:16 Inactive
@github-actions

github-actions Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

Binary size checks passed

✅ 7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
✅ baml-cli Linux 🔒 17.7 MB 7.5 MB file 24.1 MB -6.4 MB (-26.7%) OK
✅ packed-program Linux 🔒 12.9 MB 5.4 MB file 15.1 MB -2.3 MB (-14.9%) OK
✅ baml-cli macOS 🔒 13.5 MB 6.5 MB file 18.2 MB -4.7 MB (-26.0%) OK
✅ packed-program macOS 🔒 9.8 MB 4.7 MB file 11.5 MB -1.7 MB (-14.9%) OK
✅ baml-cli Windows 🔒 14.5 MB 6.7 MB file 19.6 MB -5.1 MB (-26.1%) OK
✅ packed-program Windows 🔒 10.3 MB 4.8 MB file 12.2 MB -1.9 MB (-15.5%) OK
✅ bridge_wasm WASM 11.8 MB 🔒 3.4 MB gzip 3.9 MB -518.8 KB (-13.4%) 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

2kai2kai2 added 8 commits June 2, 2026 16:49
The original interfaces implementation made `Self` an interface type, instead of a pinned type variable. For example, this is now invalid:

```baml
interface Equatable {
    function eq(self, other: Self) -> bool
}
function cmp<S extends Equatable, U extends Equatable>(x: S, y: U) -> bool {
    return x.eq(y)
}
```

Since while both `S` and `U` extend `Equatable`, they are *not* necessarily the same type and thus `eq` should not be able to be called on them. This test case now produces a compiler error.
We now use an enum for the `Self` reciever-type: interface-typed, typearg, or concrete. Each has slightly different semantics (especially interface-typed which cannot be called on methods with multiple `Self` parameters)
You cannot implement an interface for an abstract (union/interface/`unknown`) type, as they are abstract.
- Prevent implementation blocks for type aliases which refer to abstract types
- Fix `self` reciever-type resolution on out-of-body implements blocks
- Better docs
- Better diagnostics
@vercel
vercel Bot temporarily deployed to Preview – beps June 2, 2026 23:50 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 2, 2026 23:56 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 3, 2026 00:09 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps June 3, 2026 22:23 Inactive
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 3, 2026 22:28 Inactive
@2kai2kai2
2kai2kai2 added this pull request to the merge queue Jun 3, 2026
Merged via the queue into canary with commit 3991810 Jun 3, 2026
50 of 51 checks passed
@2kai2kai2
2kai2kai2 deleted the kai/comparison branch June 3, 2026 22:43
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 3, 2026 22:44 Inactive

This branch was previously deployed

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