Section 03: Type Solver Budget Infrastructure
Status: Not Started
Goal: Add explicit fuel/budget limits to the type solver so pathological generic programs terminate predictably with structured diagnostics. Currently the solver relies on the generic “recursion depth: 256” rule in impl-hygiene.md:600 and specific local limits (rank saturation at u16::MAX, resolution depth at 16, collection walk depth at 32). Missing: unification fuel, substitution depth, and trait-resolution recursion limits.
Success Criteria:
- Unification has a fuel counter that produces
E2042(solver budget exceeded) — satisfies mission criterion “solver terminates predictably” - Substitution has a depth limit that produces
E2043(substitution depth exceeded) — satisfies mission criterion “solver terminates predictably” - Trait resolution has a recursion limit with a dedicated diagnostic — satisfies mission criterion “solver terminates predictably”
- All existing tests pass unchanged (fuel limits high enough for real programs)
- At least 2 pathological test cases demonstrate the fuel limits fire correctly (TDD matrix)
Context: Research found these existing guards:
Rank(u16)saturation inori_types/src/unify/rank/mod.rs(131 lines) — prevents rank overflowMAX_DEPTH: u32 = 16inori_types/src/pool/accessors.rs:370— resolution chain limit- Collection surface walk bounded at depth 32 (
pool/collection_surface/mod.rs:36) - Triviality cycle detection via
FxHashSet<Idx>(triviality/mod.rs, 203 lines) - LLVM type resolution
MAX_RESOLVE_DEPTH: u32 = 32+ cycle detection (layout_resolver.rs:104)
Missing: no limit on total unification steps, no recursion depth limit on substitute() or substitute_in_pool(), no global type-checking budget per module.
Reference implementations:
- TypeScript
src/compiler/checker.ts:1509-1514: trackstotalInstantiationCount,instantiationCount, andinstantiationDepth; reportsType instantiation is excessively deep and possibly infiniteas a structured diagnostic - Rust
rustc_trait_selection: configurable# for trait resolution; overflow producesE0275with the chain displayed
Depends on: Section 01 (policy language).
03.1 Unification Fuel Counter
File(s): compiler/ori_types/src/unify/mod.rs, compiler/ori_types/src/infer/mod.rs
Add a fuel counter to the unification engine that tracks total unification steps per module check. This catches pathological constraint generation (e.g., exponential blowup from deeply nested generics) without affecting normal programs.
-
Write failing test:
.oriprogram with deeply nested generic instantiation that should trigger E2042- Matrix: recursive type aliases, deeply chained trait bounds, repeated instantiation
- Negative pin: program that currently type-checks must still pass
-
Add
unify_fuel: u32field toUnifyEngineinitialized toDEFAULT_UNIFY_FUEL = 10_000- Decrement on each
unify()call - When fuel reaches 0, return
Err(TypeError::SolverBudgetExceeded)with E2042 - Note: the fuel counter tracks top-level
unify()invocations, not recursive sub-calls — this makes the limit correspond to “number of constraints generated” rather than “depth of a single constraint resolution”
- Decrement on each
-
Add
E2042error code toori_types/src/type_error/check_error/— “Type solver budget exceeded: the checker generated more than N constraints. This usually indicates recursive or deeply nested generic instantiation.” -
Verify all existing tests pass with the default limit (if any test fails, the limit is too low — raise it, don’t weaken tests)
-
Subsection close-out (03.1) — MANDATORY before starting 03.2:
- All tasks above are
[x] - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection - Run
/sync-claudeto check if code changes affect CLAUDE.md or rules files
- All tasks above are
03.2 Substitution Depth Limit
File(s): compiler/ori_types/src/pool/substitute/mod.rs, compiler/ori_types/src/unify/substitute.rs
Add a recursion depth limit to substitute_in_pool() and UnifyEngine::substitute(). Both currently recurse unboundedly through the type tree.
-
Write failing test:
.oriprogram with recursive type alias that causes unbounded substitution depth -
Add
depth: u32parameter (or aSubstitutionContextwith a depth field) tosubstitute_in_pool()and propagate through recursive calls- Default limit: 256 (matches the generic
impl-hygiene.md:600depth limit) - On overflow: return
Idx::ERRORand accumulateE2043diagnostic - Apply the same limit to
UnifyEngine::substitute()inunify/substitute.rs
- Default limit: 256 (matches the generic
-
Add
E2043error code — “Substitution depth exceeded: type substitution recursed more than N levels. This usually indicates a recursive type alias or deeply nested generic instantiation.” -
Verify all existing tests pass (substitution depth of 256 should be far beyond any normal program)
-
Subsection close-out (03.2) — MANDATORY before starting 03.3:
- All tasks above are
[x] - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection - Run
/sync-claudeto check if code changes affect CLAUDE.md or rules files
- All tasks above are
03.3 Trait Resolution Recursion Limit
File(s): compiler/ori_types/src/registry/traits/mod.rs, compiler/ori_types/src/check/registration/impls.rs
Add a recursion limit for trait resolution — specifically for the supertrait chain traversal and impl-matching recursion that can diverge on circular trait hierarchies.
-
Write failing test:
.oriprogram with circular trait bounds or deeply chained supertraits -
Add resolution depth tracking to
TraitRegistry::lookup_method()and bound-satisfaction checks- Default limit: 128 (matches Rust’s default)
- On overflow: produce a diagnostic (reuse E2042 with a “trait resolution” qualifier, or add a dedicated code)
-
Verify all existing tests pass
-
Subsection close-out (03.3) — MANDATORY before starting 03.4:
- All tasks above are
[x] - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection - Run
/sync-claudeto check if code changes affect CLAUDE.md or rules files
- All tasks above are
03.4 Rule Documentation & Error Codes
File(s): .claude/rules/typeck.md, .claude/rules/impl-hygiene.md
Document the solver budget rules in the canonical rule files.
-
Add rules to
typeck.md— new section or addition to §3 Unification:- Every recursive solver loop SHALL carry explicit fuel/depth budget
- Cycle detection is mandatory for trait resolution and type normalization
- Overflow MUST produce structured diagnostics (E2042/E2043), not panics
- Cross-reference: TypeScript instantiation tracking, Rust recursion_limit
-
Update
impl-hygiene.md§Panic & Assertion line 600 — replace the generic “recursion depth: 256” with a reference to the solver-specific limits defined intypeck.md, noting that solver recursion now has domain-specific limits with domain-specific error codes -
Register E2042 and E2043 in
typeck.md§DI-1 Error Code Table -
Subsection close-out (03.4) — MANDATORY before completing section:
- All tasks above are
[x] - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection - Run
/sync-claudeto check if code changes affect CLAUDE.md or rules files
- All tasks above are
03.R Third Party Review Findings
- None.
03.N Completion Checklist
- All subsections (03.1-03.4) complete
- E2042 and E2043 error codes registered and tested
- At least 2 pathological test programs trigger the fuel limits (positive pins)
- All existing tests pass unchanged (negative pins — limits don’t affect normal code)
- Debug AND release builds pass
-
timeout 150 ./test-all.shpasses -
/tpr-review— independent dual-source review clean -
/impl-hygiene-review— implementation hygiene clean -
/improve-tooling— section-close sweep -
/sync-claude— section-close doc sync