Section 07: TypeFolder Trait
Status: Not Started
Goal: Extract the shared type-transformation recursion skeleton from 4+ substitution implementations into a TypeFolder trait. This is the aspirational pattern already documented in impl-hygiene.md §Aspirational Patterns — “A TypeFolder trait where consumers implement only the interesting cases and super_fold_with() handles recursion.”
Success Criteria:
-
TypeFoldertrait exists — satisfies mission criterion “TypeFolder eliminates 4+ duplications” - All substitution impls use the shared skeleton — satisfies mission criterion “new transformations use fold_*”
- All tests pass unchanged — behavior is identical, only the code structure changes
-
impl-hygiene.mdupdated to mark the aspirational pattern as implemented
Context: Research found 4+ substitution implementations with identical control-flow skeletons:
pool/substitute/mod.rs(360 lines):substitute_in_pool()— standalone on&mut Poolfor monomorphizationunify/substitute.rs(270 lines):UnifyEngine::substitute()— scheme instantiationinfer/expr/control_flow.rs:473,555:substitute_type_params()/substitute_type_params_with_map()— multi-clause function type paramsinfer/expr/structs/mod.rs:312:substitute_named_types()— struct contextpool/substitute/mod.rs:extract_var_from_types()— parallel-walk extraction (same skeleton)
All share: HAS_VAR flag fast-path → Tag match → recurse on children per tag layout → handle Var/BoundVar specially. The skeleton is ~50 lines of tag dispatch; only the leaf cases differ.
Reference implementations:
- Rust
rustc_type_ir/src/fold.rs:TypeFolder<I: Interner>trait withfold_ty(),fold_region(),fold_const(). Default implementations callsuper_fold_with()which handles recursion. Consumers implement only the cases they care about. - Rust
TypeSuperFoldabletrait: the “walk the children” implementation that the defaultfold_*methods delegate to.
Depends on: Section 01 (policy language). Independent of Sections 02-06.
07.1 Design TypeFolder Trait
File(s): compiler/ori_types/src/pool/ (new file fold.rs or fold/mod.rs)
Design the TypeFolder trait interface based on the shared skeleton extracted from the 4+ implementations.
-
Analyze the shared skeleton across all 4+ implementations:
- Map the tag-dispatch arms that are identical vs. the leaf cases that differ
- Identify the minimal set of
fold_*methods consumers need to override - Identify what context the folder needs (pool access, substitution map, depth counter)
-
Design the trait:
/// Trait for recursive type-to-type transformations. /// Consumers implement only the interesting cases; /// `super_fold()` handles structural recursion. pub trait TypeFolder { /// The context type providing pool access and transformation state. type Context; /// Fold a type variable. Default: return unchanged. fn fold_var(&mut self, ctx: &mut Self::Context, idx: Idx, var_id: u32) -> Idx { idx } /// Fold a bound variable. Default: return unchanged. fn fold_bound_var(&mut self, ctx: &mut Self::Context, idx: Idx, var_id: u32) -> Idx { idx } /// Fold a named type reference. Default: recurse via super_fold. fn fold_named(&mut self, ctx: &mut Self::Context, idx: Idx) -> Idx { ... } // ... other interesting-case methods } /// Structural recursion: walk children of `idx`, calling `folder.fold_*()` on each. /// This is the shared skeleton that all 4+ implementations currently duplicate. pub fn super_fold<F: TypeFolder>(folder: &mut F, ctx: &mut F::Context, pool: &mut Pool, idx: Idx) -> Idx { // HAS_VAR fast-path (from TypeFlags) // Tag dispatch → recurse on children // This is the ~50 lines of shared skeleton } -
Validate the design against all 4+ implementations — every implementation must be expressible as a
TypeFolderimpl with only leaf-case overrides -
Subsection close-out (07.1) — MANDATORY before starting 07.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
07.2 Implement Default Recursion
File(s): compiler/ori_types/src/pool/fold.rs (or fold/mod.rs)
Implement the super_fold() function — the shared recursion skeleton.
-
Write failing tests: create a test
TypeFolderimpl that counts folded types, fold a complex type tree, assert the count matches the expected traversal -
Implement
super_fold():- Extract the tag-dispatch logic from
substitute_in_pool()(the most complete implementation) - Handle all tag categories: primitives (return unchanged), single-child containers (recurse on child), two-child containers, complex types (Function, Tuple, Struct, Enum), Named/Applied, variables (delegate to
fold_var/fold_bound_var), schemes - Gate with
TypeFlags::HAS_VARfast-path (or make the gate configurable if some folders need to walk non-variable types) - Pass depth counter for the Section 03 substitution depth limit
- Extract the tag-dispatch logic from
-
Add tests for edge cases: empty types, deeply nested types, circular references (should hit occurs check), types with no variables (fast-path)
-
Subsection close-out (07.2) — MANDATORY before starting 07.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
07.3 Migrate Substitution Implementations
File(s): compiler/ori_types/src/pool/substitute/mod.rs, compiler/ori_types/src/unify/substitute.rs, compiler/ori_types/src/infer/expr/control_flow.rs, compiler/ori_types/src/infer/expr/structs/mod.rs
Rewrite each substitution implementation as a TypeFolder impl. Each migration must preserve exact behavior.
-
Migrate
substitute_in_pool()(pool/substitute/mod.rs):- Create
struct PoolSubstFolder { var_subst: &FxHashMap<u32, Idx> } - Implement
TypeFolderwithfold_var()that does the var_id lookup - Replace the 360-line implementation with the folder call
- Run all existing tests — behavior must be identical
- Create
-
Migrate
UnifyEngine::substitute()(unify/substitute.rs):- Create
struct UnifySubstFolder { ... }with link-following logic infold_var() - Replace the 270-line implementation
- Run all existing tests
- Create
-
Migrate
substitute_type_params()andsubstitute_type_params_with_map()(infer/expr/control_flow.rs):- These are simpler — may become direct calls to the pool substitution folder with a different var map
-
Migrate
substitute_named_types()(infer/expr/structs/mod.rs):- Similar simplification
-
Migrate
extract_var_from_types()— this is a parallel-walk extraction, not a substitution. It may need aTypeVisitor(read-only fold) rather than aTypeFolder(mutating fold). If the skeleton is different enough, keep it separate but document why. -
Verify:
timeout 150 ./test-all.sh— all tests pass, no behavioral changes -
Subsection close-out (07.3) — MANDATORY before starting 07.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
07.4 Cleanup & Documentation
File(s): .claude/rules/impl-hygiene.md, .claude/rules/types.md
-
Update
impl-hygiene.md§Aspirational Patterns → TypeFolder: change from “aspirational” to “implemented”. Note the adoption path that was followed. -
Add
TypeFoldertotypes.md§Key Files or as a new section — document the trait, its methods, and how to add new type transformations -
Delete dead code — the old substitution implementations that were replaced by folder impls. Verify no callers remain.
-
Subsection close-out (07.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
07.R Third Party Review Findings
- None.
07.N Completion Checklist
- All subsections (07.1-07.4) complete
-
TypeFoldertrait exists withsuper_fold()recursion - All 4+ substitution implementations migrated to the trait
- Net code reduction (expect ~400-500 lines removed from duplicated skeletons)
- All existing tests pass unchanged (behavior identical)
- 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