Skip to content

Fix for equirecursive equivalence algebra - #4311

Merged
2kai2kai2 merged 4 commits into
canaryfrom
kai/equirecursive-equivalence
Jul 31, 2026
Merged

Fix for equirecursive equivalence algebra#4311
2kai2kai2 merged 4 commits into
canaryfrom
kai/equirecursive-equivalence

Conversation

@2kai2kai2

@2kai2kai2 2kai2kai2 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Implements a more principled approach which produces a unique minimal representation of the potentially infinite type tree that would be produced if type aliases were fully unfolded. Unlike the previous version of this, it is also invariant over type alias binder names (so type A = A[] and type B = B[] should be identical since their infinitely expanded type trees are equivalent)

Summary by CodeRabbit

  • New Features

    • Added support for transparent, recursive, and mutually recursive type aliases.
    • Improved type equivalence, subtyping, overlap detection, and normalization for recursive types.
    • Added support for generic implementations using json types.
    • Improved handling of complete boolean unions and recursive type rendering.
  • Bug Fixes

    • Improved handling of recursive aliases nested in futures.
    • Corrected implementation dispatch for normalized type subjects.
    • Added clearer diagnostics for unguarded recursive definitions.

@vercel

vercel Bot commented Jul 31, 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 Jul 31, 2026 8:45pm
promptfiddle2 Ready Ready Preview Jul 31, 2026 8:45pm

Request Review

@github-actions

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.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds μ-canonicalization for recursive aliases. It updates normalization, compiler coherence, recursive cycle validation, overlap checks, runtime documentation, and regression coverage.

Changes

Recursive type normalization

Layer / File(s) Summary
Recursive type contracts
baml_language/TYPE_SYSTEM.md, baml_language/crates/baml_compiler2_tir/src/type_context.rs, baml_language/crates/bex_vm/src/type_context.rs
The type rules define transparent aliases, productive recursion, equirecursive identity, and canonical normalization.
Phase-aware normalization
baml_language/crates/baml_type/src/normalize.rs
Normalization uses named and canonical phases, indexed recursion variables, μ-aware type algebra, subtyping, disjointness, rendering, and interface reconstruction.
μ automaton canonicalization
baml_language/crates/baml_type/src/normalize/mu.rs
The pipeline builds recursive term graphs, computes closures, minimizes bisimilar states, applies union algebra, reads back canonical terms, and renders recursive aliases.
Compiler integration
baml_language/crates/baml_compiler2_tir/src/normalize.rs, baml_language/crates/baml_compiler2_tir/src/interfaces/coherence.rs
Compiler checks traverse Future types and validate normalized implementation subjects under implementation-specific bounds.
Regression coverage
baml_language/crates/baml_compiler2_tir/src/unify.rs, baml_language/crates/baml_type/src/normalize/tests.rs, baml_language/crates/baml_tests/baml_src/ns_json_alias/json_alias.baml
Tests cover recursive aliases, guarded cycles, boolean and enum unions, overlap results, rendering, and JSON generic dispatch.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TypeInput
  participant Normalizer
  participant MuCanonicalizer
  participant CompilerChecks
  TypeInput->>Normalizer: parse aliases and constructors
  Normalizer->>MuCanonicalizer: canonicalize recursive terms
  MuCanonicalizer-->>Normalizer: return canonical type and recursive display
  Normalizer->>CompilerChecks: provide normalized implementation subjects
  CompilerChecks-->>TypeInput: resolve equivalence, overlap, or coherence
Loading

Possibly related PRs

Suggested reviewers: codeshaunted, hellovai

Poem

A rabbit traces μ through every loop,
Canonical aliases form a group.
Graphs close and states align,
Recursive names remain benign.
Tests hop through unions bright.
Future cycles guard the night.

🚥 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 clearly and concisely describes the main change: fixing equirecursive type equivalence.
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.
✨ 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/equirecursive-equivalence

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.

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

🧹 Nitpick comments (5)
baml_language/crates/baml_type/src/normalize/mu.rs (2)

636-704: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider one shared Tarjan helper for both SCC passes.

epsilon_sccs and cyclic_states (Lines 1332-1404) duplicate the same Tarjan state machine. They differ only in the successor set and in what they record per component. Extract one helper that takes a successor function and a per-component callback, then build both results on top of it. This removes about 60 duplicated lines and keeps the two traversals in sync when the edge model changes.

🤖 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_type/src/normalize/mu.rs` around lines 636 - 704,
Extract the duplicated Tarjan traversal used by epsilon_sccs and cyclic_states
into one shared helper that accepts a successor-producing function and a
per-component callback or recording strategy. Preserve each function’s existing
edge filtering and component-result behavior while routing both passes through
the shared state machine, and remove their separate Tarjan implementations.

894-907: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Write through resolve(s) to keep this arm safe under future changes.

Today every s from auto.reachable(root) is live when it is processed, because only the current s is redirected inside the loop. If a later change adds a redirect from collapse_complete_enums or collapse_complete_bools, auto.states[s].node would write into a dead state while auto.node(s) reads the live one, and the algebra result would be silently dropped. Resolve s once at the top of the iteration and use that id for the writes.

🤖 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_type/src/normalize/mu.rs` around lines 894 - 907,
Update the iteration in the enum-collapse logic around the shown members.len()
match to resolve s once at the start of each iteration, then use the resolved
state ID for all auto.states writes and related node operations. Preserve the
existing redirect and changed behavior while ensuring writes target the live
state if collapse_complete_enums or collapse_complete_bools redirects the
original ID.
baml_language/crates/baml_compiler2_tir/src/unify.rs (1)

1407-1415: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename this test to say α-equivalent, not distinct.

distinct_recursive_alias_subjects_overlap (line 1407) and distinct_recursive_alias_subjects_are_disjoint (line 1464) share a prefix but assert opposite outcomes. In this test only the alias names differ; the denoted types are identical, which is why the result is Overlap::Yes. The current name reads as if two different types overlap.

♻️ Suggested rename
-    fn distinct_recursive_alias_subjects_overlap() {
+    fn alpha_equivalent_recursive_alias_subjects_overlap() {

Update the cross-reference in distinct_recursive_alias_subjects_are_disjoint accordingly.

🤖 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/unify.rs` around lines 1407 -
1415, Rename the test `distinct_recursive_alias_subjects_overlap` to indicate
that the aliases are α-equivalent, reflecting that different names denote
identical types and produce `Overlap::Yes`; update the corresponding
cross-reference in `distinct_recursive_alias_subjects_are_disjoint` while
leaving test behavior unchanged.
baml_language/crates/baml_type/src/normalize/tests.rs (1)

1521-1523: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the idempotence assertion here to match the sibling totality tests.

nested_union_member_recursion_renders_totally (line 1483) and map_value_union_recursion_renders_totally (line 1538) both assert ctx.normalize(&n) == n. This test omits it, so a rendering that is equivalent but not stable under a second pass would still pass. The projection-qualifier cut is the case most likely to produce an unstable rendering.

♻️ Suggested addition
     let n = ctx.normalize(&alias("A"));
     assert!(equivalent(&alias("A"), &n, &ctx));
+    assert_eq!(ctx.normalize(&n), n, "idempotent");
 }
🤖 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_type/src/normalize/tests.rs` around lines 1521 -
1523, Add an idempotence assertion to the test around the existing `let n =
ctx.normalize(&alias("A"));` statement, verifying that normalizing `n` again is
equivalent to `n`, consistent with
`nested_union_member_recursion_renders_totally` and
`map_value_union_recursion_renders_totally`.
baml_language/crates/baml_type/src/normalize.rs (1)

2444-2512: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider making the replace_rec_var match exhaustive.

Line 2510 uses _ => self.clone() for leaves and non-matching indices. The sibling walks in this file (contains_mu at Line 562, has_free_rec_var at Line 2391, has_unguarded_mu at Line 2221, resolve_binders at Line 1395) all list every variant, so the compiler forces an update when a variant gains children. replace_rec_var loses that protection: a new variant that carries a NormalTy child would be cloned without substitution, and the resulting term would keep a stale RecVar. That failure is silent.

List the leaf variants explicitly and keep RecVar(_) as the only non-matching-index arm.

♻️ Proposed exhaustive arm
-            // Leaves and non-matching indices are untouched.
-            _ => self.clone(),
+            // Leaves and non-matching indices are untouched.
+            NormalTy::RecVar(_)
+            | NormalTy::Int
+            | NormalTy::Bigint
+            | NormalTy::Float
+            | NormalTy::String
+            | NormalTy::Bool
+            | NormalTy::Null
+            | NormalTy::Uint8Array
+            | NormalTy::Media(_)
+            | NormalTy::Void
+            | NormalTy::RustType
+            | NormalTy::Type
+            | NormalTy::Resource
+            | NormalTy::PromptAst
+            | NormalTy::Literal(_)
+            | NormalTy::Enum(_)
+            | NormalTy::EnumVariant(_, _)
+            | NormalTy::TypeVar(_)
+            | NormalTy::OpaqueAlias(_)
+            | NormalTy::Never
+            | NormalTy::BuiltinUnknown
+            | NormalTy::Unknown
+            | NormalTy::Error => self.clone(),
🤖 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_type/src/normalize.rs` around lines 2444 - 2512,
Make the match in NormalTy::replace_rec_var exhaustive: replace the catch-all
arm with explicit arms for every leaf variant, and retain RecVar(_) as the only
arm for non-matching recursive-variable indices. Preserve recursive substitution
for all variants containing NormalTy children so future variants cannot silently
bypass replacement.
🤖 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/interfaces/coherence.rs`:
- Around line 172-181: In package_coherence_diagnostics, precompute each owned
and dependency impl’s normalized for_ty_pattern and its is_valid_impl_subject
result once using the existing aliases and bounds context. Update impls_conflict
and the overlap loops over own[i+1..] and dep_impls to consume these cached
subjects and validity flags, removing repeated normalize calls while preserving
the current invalid-subject behavior.

In `@baml_language/crates/baml_type/src/normalize.rs`:
- Around line 770-781: Guard the μ-unfolding branches in is_disjoint_from with
recursion tracking or a depth/assumption limit before calling unfold. Ensure
repeated non-contractive μ terms terminate conservatively instead of re-entering
the same unfolding through union decomposition, while preserving normal
structural comparisons for guarded, contractive μ types.

In `@baml_language/crates/baml_type/src/normalize/mu.rs`:
- Around line 1686-1710: Update the Node::Projection handling in the renderer so
a qualifier that is not Node::Interface returns None instead of reaching
unreachable!. Preserve the existing interface rendering path and let the caller
use its normal fallback behavior for unsupported qualifier shapes.
- Around line 846-853: Introduce a per-pass cache for read_back results keyed by
resolved StateId, and update the materialization logic in minimize_and_absorb to
reuse cached values instead of invoking read_back for every member. Clear
rb_cache in every match members.len() arm that mutates a node or adds a
redirect, ensuring subsequent absorption decisions use fresh read-back results.

---

Nitpick comments:
In `@baml_language/crates/baml_compiler2_tir/src/unify.rs`:
- Around line 1407-1415: Rename the test
`distinct_recursive_alias_subjects_overlap` to indicate that the aliases are
α-equivalent, reflecting that different names denote identical types and produce
`Overlap::Yes`; update the corresponding cross-reference in
`distinct_recursive_alias_subjects_are_disjoint` while leaving test behavior
unchanged.

In `@baml_language/crates/baml_type/src/normalize.rs`:
- Around line 2444-2512: Make the match in NormalTy::replace_rec_var exhaustive:
replace the catch-all arm with explicit arms for every leaf variant, and retain
RecVar(_) as the only arm for non-matching recursive-variable indices. Preserve
recursive substitution for all variants containing NormalTy children so future
variants cannot silently bypass replacement.

In `@baml_language/crates/baml_type/src/normalize/mu.rs`:
- Around line 636-704: Extract the duplicated Tarjan traversal used by
epsilon_sccs and cyclic_states into one shared helper that accepts a
successor-producing function and a per-component callback or recording strategy.
Preserve each function’s existing edge filtering and component-result behavior
while routing both passes through the shared state machine, and remove their
separate Tarjan implementations.
- Around line 894-907: Update the iteration in the enum-collapse logic around
the shown members.len() match to resolve s once at the start of each iteration,
then use the resolved state ID for all auto.states writes and related node
operations. Preserve the existing redirect and changed behavior while ensuring
writes target the live state if collapse_complete_enums or
collapse_complete_bools redirects the original ID.

In `@baml_language/crates/baml_type/src/normalize/tests.rs`:
- Around line 1521-1523: Add an idempotence assertion to the test around the
existing `let n = ctx.normalize(&alias("A"));` statement, verifying that
normalizing `n` again is equivalent to `n`, consistent with
`nested_union_member_recursion_renders_totally` and
`map_value_union_recursion_renders_totally`.
🪄 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 Plus

Run ID: 15d582e3-fffd-450e-b739-f13ebf9962ed

📥 Commits

Reviewing files that changed from the base of the PR and between 098a8ce and 05414c4.

⛔ Files ignored due to path filters (8)
  • baml_language/crates/baml_tests/snapshots/baml_src/json_alias.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/json_parse_stringify.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/json_to_from_string.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/json_llm_return_type/baml_tests__compiles__json_llm_return_type__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/json_map_literal/baml_tests__compiles__json_map_literal__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/json_parse_stringify_intrinsics/baml_tests__compiles__json_parse_stringify_intrinsics__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/json_parse_stringify_intrinsics/baml_tests__compiles__json_parse_stringify_intrinsics__06_codegen.snap is excluded by !**/*.snap
📒 Files selected for processing (10)
  • baml_language/TYPE_SYSTEM.md
  • baml_language/crates/baml_compiler2_tir/src/interfaces/coherence.rs
  • baml_language/crates/baml_compiler2_tir/src/normalize.rs
  • baml_language/crates/baml_compiler2_tir/src/type_context.rs
  • baml_language/crates/baml_compiler2_tir/src/unify.rs
  • baml_language/crates/baml_tests/baml_src/ns_json_alias/json_alias.baml
  • baml_language/crates/baml_type/src/normalize.rs
  • baml_language/crates/baml_type/src/normalize/mu.rs
  • baml_language/crates/baml_type/src/normalize/tests.rs
  • baml_language/crates/bex_vm/src/type_context.rs

Comment thread baml_language/crates/baml_compiler2_tir/src/interfaces/coherence.rs Outdated
Comment thread baml_language/crates/baml_type/src/normalize.rs
Comment thread baml_language/crates/baml_type/src/normalize/mu.rs
Comment thread baml_language/crates/baml_type/src/normalize/mu.rs
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

Binary size checks passed

7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
baml-cli Linux 🔒 27.5 MB 11.7 MB file 27.4 MB +178.0 KB (+0.7%) OK
packed-program Linux 🔒 17.9 MB 7.4 MB file 17.7 MB +219.7 KB (+1.2%) OK
baml-cli macOS 🔒 21.4 MB 10.3 MB file 21.3 MB +130.4 KB (+0.6%) OK
packed-program macOS 🔒 14.0 MB 6.5 MB file 13.8 MB +160.9 KB (+1.2%) OK
baml-cli Windows 🔒 23.1 MB 10.4 MB file 23.0 MB +97.2 KB (+0.4%) OK
packed-program Windows 🔒 14.9 MB 6.6 MB file 14.8 MB +153.7 KB (+1.0%) OK
bridge_wasm WASM 17.0 MB 🔒 4.6 MB gzip 4.6 MB +26.8 KB (+0.6%) 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: 1

🤖 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/interfaces/coherence.rs`:
- Around line 87-95: Update package_coherence_diagnostics to populate bounds
from the current impl’s declared generic parameters, matching
validate_impl_signatures by collecting data.generic_params into TypeVarBoundsMap
instead of using default(). Ensure the resulting GlobalTypeContext passes this
impl-specific map to PreparedImpl::valid_subject and overlap checks.
🪄 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 Plus

Run ID: c3c76a23-8ecf-468c-bf50-1db0055e1b3e

📥 Commits

Reviewing files that changed from the base of the PR and between 05414c4 and 971b678.

📒 Files selected for processing (4)
  • baml_language/crates/baml_compiler2_tir/src/interfaces/coherence.rs
  • baml_language/crates/baml_type/src/normalize.rs
  • baml_language/crates/baml_type/src/normalize/mu.rs
  • baml_language/crates/baml_type/src/normalize/tests.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • baml_language/crates/baml_type/src/normalize/mu.rs
  • baml_language/crates/baml_type/src/normalize.rs

Comment thread baml_language/crates/baml_compiler2_tir/src/interfaces/coherence.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
baml_language/crates/baml_type/src/normalize.rs (1)

1968-1974: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make the projection qualifier reconstruction handle into_interface() failures.

into_interface() returns None when a μ display is not Ty::Interface, but NormalTy::AssociatedTypeProjection::into_ty() currently treats that case with unreachable!. If reconstruction is allowed to degrade conservatively, use the existing Option<Interface> path; otherwise reject the projection before reaching this arm. Add a regression test for a recursive projection with an interface-shaped binding that produces a non-interface normal qualifier.

🤖 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_type/src/normalize.rs` around lines 1968 - 1974,
Update NormalTy::AssociatedTypeProjection::into_ty() to handle into_interface()
returning None instead of reaching unreachable!, using the existing
Option<Interface> representation or rejecting the projection before this arm.
Preserve valid interface reconstruction and add a regression test covering a
recursive projection whose interface-shaped binding yields a non-interface
normal qualifier.
🧹 Nitpick comments (1)
baml_language/crates/baml_type/src/normalize.rs (1)

2500-2532: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make replace_rec_var exhaustive.

Replace the final catch-all arm with explicit leaf variants and a separate RecVar(_) arm for non-matching indices. A future NormalTy variant with recursive children could otherwise bypass substitution and leave stale recursion variables.

🤖 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_type/src/normalize.rs` around lines 2500 - 2532,
Update the match in replace_rec_var to remove the final catch-all arm, enumerate
every leaf NormalTy variant explicitly as unchanged, and add a separate
RecVar(_) arm for recursion variables whose index does not match. Ensure
variants containing recursive children remain explicitly handled so future
additions cannot bypass substitution.
🤖 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_type/src/normalize.rs`:
- Around line 1968-1974: Update NormalTy::AssociatedTypeProjection::into_ty() to
handle into_interface() returning None instead of reaching unreachable!, using
the existing Option<Interface> representation or rejecting the projection before
this arm. Preserve valid interface reconstruction and add a regression test
covering a recursive projection whose interface-shaped binding yields a
non-interface normal qualifier.

---

Nitpick comments:
In `@baml_language/crates/baml_type/src/normalize.rs`:
- Around line 2500-2532: Update the match in replace_rec_var to remove the final
catch-all arm, enumerate every leaf NormalTy variant explicitly as unchanged,
and add a separate RecVar(_) arm for recursion variables whose index does not
match. Ensure variants containing recursive children remain explicitly handled
so future additions cannot bypass substitution.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0bf8d904-5c60-4046-bd20-a08d9fc475a4

📥 Commits

Reviewing files that changed from the base of the PR and between 971b678 and 2147f9a.

📒 Files selected for processing (4)
  • baml_language/crates/baml_compiler2_tir/src/interfaces/coherence.rs
  • baml_language/crates/baml_type/src/normalize.rs
  • baml_language/crates/baml_type/src/normalize/mu.rs
  • baml_language/crates/baml_type/src/normalize/tests.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • baml_language/crates/baml_type/src/normalize/tests.rs
  • baml_language/crates/baml_compiler2_tir/src/interfaces/coherence.rs
  • baml_language/crates/baml_type/src/normalize/mu.rs

@2kai2kai2
2kai2kai2 added this pull request to the merge queue Jul 31, 2026
Merged via the queue into canary with commit 2173440 Jul 31, 2026
82 checks passed
@2kai2kai2
2kai2kai2 deleted the kai/equirecursive-equivalence branch July 31, 2026 21:03
codeshaunted added a commit that referenced this pull request Aug 13, 2026
Brings the branch up to date with canary (146 conflicted files) and ports
every TIR-side change onto the hir_ty substrate:

- #4311 equirecursive types: canary's mu-automaton (phase-typed
  NormalTy<Named|Canonical>, de Bruijn binders, canonical_bottom_up)
  merged with this branch's B-1091 co-inductive assumption threading;
  the interned entry re-phased through the same pipeline. Same-shape
  recursive aliases now provably overlap (coherence twin updated).
- #4320 tooling surface: hir_ty gains callable::function_signature_ty
  (a view over function_signature; own generics only; Self resolved for
  interface-default and free-impl methods), interfaces::
  {InterfaceDeclScope, resolve_interface_fields,
  resolve_interface_required_methods, interfaces_declaring_associated_type},
  and package_interface::exported_function (one place pairing the
  signature query with the effective-throws oracle). baml_surface is
  re-pointed tir -> hir_ty with its facts.rs contract intact.
- #4308 catch-arm interface facts: reachability respects implements in
  both directions (the semantic contract compiles with zero diagnostics);
  the arm lowering keeps the modular Interface narrow rather than the
  closed-world concrete residual.
- #4291 intersection bounds, consumption side: GenericParamData conjunction
  shapes adopted end to end; class constructor sites register one
  Implements obligation per declared bound conjunct (register_class_bounds,
  rustc's ADT well-formedness discipline); interface-member lookup pools
  declarers across the bound conjunction, dedupes by realized identity
  through requires, and reports E0121/E0122 ambiguity (rustc's
  MethodError::Ambiguity shape) instead of first-conjunct-wins; a failing
  blanket-impl bound names the unsatisfied conjunct
  (BlanketBoundNotSatisfied) instead of a bare mismatch.
- #4352 @spec/ai builtins compile and emit on hir_ty. Four pre-existing
  engine gaps they exposed are fixed:
  * a bare AnyFunction's unpinned members read as their declared
    `unknown` defaults at the oracle and in the engine's same-interface
    unification (BEP-062 lazy default; no eager fill at lowering);
  * binding a bounded inference var by direct unification now REPLAYS its
    accumulated VarBounds against the solution (take_solved_class_bounds +
    replay in the finish fixpoint) instead of dropping them - the
    map-lambda + future.all shape no longer strands its type args;
  * an empty container literal flowing into a ground `unknown` demand
    commits its establishment slots to the top type (the demand is a
    consuming use); a literal with NO demand keeps the strict
    uninferrable-container error;
  * required interface methods (bodyless items under the unified method
    model) are excluded from the codegen symbol pool.
- Coherence: canary's PreparedImpl refactor adopted on Facts::with_bounds -
  the subject-validity gate now judges the same normalized spelling E0138
  judges, memoized per impl.
- baml_project CFG dispatch (canary's interface virtual-call resolution in
  the visualization) re-pointed onto hir_ty's InferenceResult and
  impls_for_type.
- check.rs: the legacy jinja prompt checker is deleted with canary's
  `client<llm>`/jinja removal; the associated-type declarer walk uses the
  conjunction-deduping interfaces_declaring_associated_type.
- Tests: fixtures migrated off removed builtins (baml.deep_equals ->
  ops.Equals `==`; a local generic pair fn where the fixture probes
  inference); snapshots re-blessed for the unified required-method model
  ([missing] items), canary's builtin/std content, and canonical union
  order.
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