Section 14: Enforcement Tests, Testing Matrix & Exit Criteria
Context: Sections 09-13 wired all consuming phases (ori_types, ori_eval, ori_arc, ori_llvm, ori_ir) to read from ori_registry instead of maintaining independent type knowledge. This section is the final gate: it replaces the remaining ~295-line consistency.rs and ~371-line dispatch_coverage.rs (which still contains a 189-entry METHODS_NOT_YET_IN_EVAL allowlist and a COLLECTION_RESOLVER_METHODS list) with a small set of structural enforcement tests that derive their expectations directly from the registry. No manual lists. No gap tracking. No “known missing” arrays. The registry IS the specification; the enforcement tests verify that every phase faithfully implements it.
Design rationale: The old consistency tests were necessary because type knowledge was scattered: TYPECK_BUILTIN_METHODS (390 entries), EVAL_BUILTIN_METHODS (~165 entries), BUILTIN_METHODS in ori_ir (123 entries), BuiltinTable in ori_llvm (179 entries), and borrowing_builtins in ori_arc. The allowlists (TYPECK_METHODS_NOT_IN_EVAL, EVAL_METHODS_NOT_IN_IR, etc.) tracked intentional gaps between these independent lists. Sections 09-10 eliminated 4 of 6 allowlists; Section 13 eliminates the remaining 2. With the registry as single source of truth, these gaps become structural impossibilities — a method either exists in the registry (and all phases must handle it) or it does not exist (and no phase references it). The enforcement tests verify this invariant at test time, while Rust’s type system enforces it at compile time (adding a field to TypeDef is a compile error in every consuming phase).
Parallel lists NOT targeted for elimination (with rationale):
The following parallel lists in consuming crates are intentionally KEPT because they encode semantic knowledge beyond the registry’s scope (COW mutation protocol, ARC pipeline internals, type-checker inference heuristics):
CONSUMING_RECEIVER_METHOD_NAMES(10 entries,ori_arc/borrow/builtins/mod.rs:44) — COW list methods that consume the receiver buffer. These are ARC pipeline semantics (mutation protocol), not type specifications. The registry’sOwnership::Borrowis insufficient because the same method name (add,concat) is borrowing for strings but consuming for lists — the type check happens at the call site inannotate_arg_ownership.CONSUMING_SECOND_ARG_METHOD_NAMES(2 entries,ori_arc/borrow/builtins/mod.rs:65) — COW list methods consuming both receiver and second arg. Same rationale as above.CONSUMING_RECEIVER_ONLY_METHOD_NAMES(5 entries,ori_arc/borrow/builtins/mod.rs:82) — Map/Set COW methods consuming only receiver. Same rationale.SHARING_METHOD_NAMES(2 entries,ori_arc/borrow/builtins/mod.rs:194) — Methods returning values sharing receiver’s backing storage (slice,substring). This is a uniqueness analysis concern, not a type specification.CODEGEN_ALIASES(2 entries,ori_llvm/builtins/tests.rs:44) — Test-only mapping (length→len,is_equal→equals). These are codegen naming conventions for theBuiltinTabletest, not parallel type knowledge.TRAIT_DISPATCH_METHODS(4 entries,ori_llvm/builtins/tests.rs:53) — Test-only list of comparison predicates from trait desugaring (is_less,is_greater, etc.). These are NOT in the registry because the type checker resolves them through trait dispatch, not builtin method dispatch.INFINITE_CONSUMING_METHODS(5 entries,ori_types/infer/expr/calls/method_call.rs:395) — Type checker inference heuristic for infinite iterator detection. Behavioral, not declarative.BOUNDING_METHODS(1 entry,ori_types/infer/expr/calls/method_call.rs:412) — Type checker inference heuristic. Same rationale.RANGE_FLOAT_ITERATION_METHODS(3 entries,ori_types/infer/expr/methods/mod.rs:28) — Type checker validation for float range iteration errors. Behavioral.WELL_KNOWN_GENERIC_TYPES(7 entries,consistency.rs:234) — This one IS targeted for registry derivation in 14.2 Test 10 below.
Future consideration: Items 1-4 (COW/sharing semantics) could eventually move to the registry as a CowBehavior or MutationProtocol field on MethodDef. This is tracked but deferred — the COW protocol is still evolving and premature centralization would create churn. Items 5-9 are genuinely behavioral and should stay in their respective crates.
What this section replaces (post-Section 13 state):
compiler/oric/src/eval/tests/methods/consistency.rs(~295 lines remaining after Section 13) — rewritten with enforcement testscompiler/oric/src/eval/tests/methods/dispatch_coverage.rs(~371 lines) — containsevery_registry_method_has_eval_dispatch_handler(already exists but with 189-entryMETHODS_NOT_YET_IN_EVALallowlist andCOLLECTION_RESOLVER_METHODSlist); the allowlists must be eliminated so the test becomes zero-exception- 9 remaining consistency tests (registry sorting, iterator consistency, 6 format spec tests, well-known generics)
EVAL_BUILTIN_METHODSandTYPECK_BUILTIN_METHODS(already eliminated in Sections 09-10)ITERATOR_METHOD_NAMES(already eliminated in Section 10)- All
resolve_*_method()functions (already eliminated in Section 09, 19 functions excludingresolve_named_type_method)
14.1 Registry-Level Integrity Tests (ori_registry)
File: compiler/ori_registry/src/tests.rs (additions to existing purity tests from Section 02)
These tests enforce internal registry consistency — the data itself is well-formed, regardless of whether consuming phases handle it correctly.
Test 1: No duplicate methods within any type
/// Every TypeDef's method list must contain unique method names.
/// A duplicate would cause ambiguous dispatch in every consuming phase.
#[test]
fn no_duplicate_methods() {
use std::collections::BTreeSet;
for type_def in BUILTIN_TYPES {
let mut seen = BTreeSet::new();
for method in type_def.methods {
assert!(
seen.insert(method.name),
"Duplicate method `{}` on type `{}`",
method.name, type_def.name,
);
}
}
}
Test 2: No empty types
/// Every TypeDef must have at least one method.
/// A type with zero methods provides no behavioral specification
/// and should not be in the registry.
#[test]
fn no_empty_types() {
for type_def in BUILTIN_TYPES {
assert!(
!type_def.methods.is_empty(),
"TypeDef `{}` has zero methods -- every registered type must \
have at least one method (minimally: clone, equals, to_str)",
type_def.name,
);
}
}
Test 3: All TypeTag variants have a TypeDef
/// Every TypeTag variant that represents a type with methods must have a
/// corresponding TypeDef in BUILTIN_TYPES. Variants with zero methods
/// (Unit, Never, Function) are intentionally excluded.
/// DoubleEndedIterator is excluded because it aliases to Iterator via
/// TypeTag::base_type().
#[test]
fn all_type_tags_present() {
use std::collections::BTreeSet;
let registered_tags: BTreeSet<TypeTag> = BUILTIN_TYPES
.iter()
.map(|td| td.tag)
.collect();
// TypeTag variants that intentionally have no TypeDef:
// - Unit, Never: no methods, no operators
// - Function: no methods (memory classification only)
// - DoubleEndedIterator: aliased to Iterator via base_type()
let excluded = [
TypeTag::Unit,
TypeTag::Never,
TypeTag::Function,
TypeTag::DoubleEndedIterator,
];
for tag in TypeTag::all() {
if excluded.contains(tag) {
continue;
}
assert!(
registered_tags.contains(tag),
"TypeTag::{tag:?} has no TypeDef in BUILTIN_TYPES. \
Add a const TypeDef in ori_registry/src/defs/ and include \
it in BUILTIN_TYPES.",
);
}
}
Test 4: Methods sorted by name within each type
/// Methods within each TypeDef must be sorted alphabetically by name.
/// This is a convention for deterministic iteration, readable diffs,
/// and binary-searchable lookup.
#[test]
fn methods_sorted_by_name() {
for type_def in BUILTIN_TYPES {
for window in type_def.methods.windows(2) {
assert!(
window[0].name <= window[1].name,
"Methods not sorted in `{}`: `{}` > `{}`\n\
Methods must be alphabetically sorted within each TypeDef.",
type_def.name, window[0].name, window[1].name,
);
}
}
}
Test 5: All receivers have explicit Ownership
/// Every MethodDef must have an explicit Ownership value for its receiver.
/// This test documents the invariant rather than checking it structurally
/// (since Ownership is a required field, Rust already enforces this at
/// compile time). The test verifies the semantic convention that no
/// method has Ownership::Owned unless it truly consumes self.
#[test]
fn all_receivers_documented() {
for type_def in BUILTIN_TYPES {
for method in type_def.methods {
// Copy types should always borrow (borrow == copy for Copy types,
// but the annotation documents the intent).
if type_def.memory == MemoryStrategy::Copy {
assert_eq!(
method.receiver, Ownership::Borrow,
"Method `{}.{}` on a Copy type should use Ownership::Borrow \
(Copy types are trivially borrowed)",
type_def.name, method.name,
);
}
// Arc types: most methods borrow, but consuming methods (into)
// may use Owned. Just verify the field is explicitly set.
// (No-op assertion -- Ownership is a required field. This test
// exists to document the expectation.)
let _ = method.receiver; // field access proves it exists
}
}
}
Test 6: Equality support is universal
/// Equality is provided via EITHER OpStrategy::eq (primitives) OR an `equals`
/// method (Eq trait). Types with neither are intentionally excluded:
/// Error (trace-compared), Channel (runtime handle), Iterator (stateful),
/// Range (generic, dispatched through type checker).
#[test]
fn no_unsupported_eq() {
let excluded = [
TypeTag::Error,
TypeTag::Channel,
TypeTag::Iterator,
TypeTag::Range,
];
for type_def in BUILTIN_TYPES {
if excluded.contains(&type_def.tag) {
continue;
}
let has_op_eq = type_def.operators.eq != OpStrategy::Unsupported;
let has_equals_method = type_def.methods.iter().any(|m| m.name == "equals");
assert!(
has_op_eq || has_equals_method,
"Type `{}` has neither OpStrategy::eq nor an `equals` method. \
All Ori types must support equality via one mechanism.",
type_def.name,
);
}
}
Test 7: Operator consistency
/// If a type supports comparison operators (lt, gt, lt_eq, gt_eq), it must
/// also support equality (eq, neq). Comparison without equality is nonsensical.
/// Additionally, if any ordering operator is supported, all four ordering
/// operators (lt, gt, lt_eq, gt_eq) must be supported.
#[test]
fn operator_consistency() {
for type_def in BUILTIN_TYPES {
let ops = &type_def.operators;
// If any comparison operator is supported, eq must be too
let has_any_cmp = ops.lt != OpStrategy::Unsupported
|| ops.gt != OpStrategy::Unsupported
|| ops.lt_eq != OpStrategy::Unsupported
|| ops.gt_eq != OpStrategy::Unsupported;
if has_any_cmp {
assert!(
ops.eq != OpStrategy::Unsupported,
"Type `{}` supports comparison but not equality. \
If lt/gt/le/ge are supported, eq must be too.",
type_def.name,
);
assert!(
ops.neq != OpStrategy::Unsupported,
"Type `{}` supports comparison but not not-equal. \
If lt/gt/le/ge are supported, neq must be too.",
type_def.name,
);
}
// If all four comparison operators are supported, they should
// use the same strategy (no mixing signed/unsigned/float)
let cmp_ops = [ops.lt, ops.gt, ops.lt_eq, ops.gt_eq];
let supported_cmp: Vec<_> = cmp_ops
.iter()
.filter(|s| **s != OpStrategy::Unsupported)
.collect();
if supported_cmp.len() > 1 {
let first = supported_cmp[0];
for s in &supported_cmp[1..] {
assert_eq!(
*s, first,
"Type `{}` uses mixed comparison strategies: {:?} vs {:?}. \
All comparison operators should use the same strategy.",
type_def.name, first, s,
);
}
}
}
}
Test 8: TypeTag::all() contains every variant
/// Verify that `TypeTag::all()` is synchronized with the enum definition.
/// If someone adds a new `TypeTag` variant but forgets to add it to
/// `ALL_TYPE_TAGS`, find_type() and iteration queries silently miss it.
///
/// This test counts variants via `size_of_val` / stride estimation and
/// compares against `TypeTag::all().len()`. The exact count (23) is
/// also asserted as a regression guard.
#[test]
fn type_tag_all_contains_every_variant() {
// Hard-coded count serves as regression guard.
// Update both the assertion AND ALL_TYPE_TAGS when adding variants.
let expected_count = 23;
assert_eq!(
TypeTag::all().len(),
expected_count,
"TypeTag::all() has {} entries but expected {expected_count}. \
A TypeTag variant was added/removed without updating ALL_TYPE_TAGS.",
TypeTag::all().len(),
);
// Verify no duplicates
let mut seen = std::collections::BTreeSet::new();
for tag in TypeTag::all() {
assert!(
seen.insert(tag),
"Duplicate TypeTag::{tag:?} in TypeTag::all()",
);
}
}
Test 9: SelfType returns are valid
/// Methods that return SelfType must be on types where returning Self
/// makes semantic sense. This test verifies that SelfType is only used
/// for methods that truly return the same type as the receiver.
///
/// Specifically: trait methods like `clone` should return SelfType.
/// Conversion methods like `to_int` should return a concrete TypeTag.
#[test]
fn self_type_returns_valid() {
for type_def in BUILTIN_TYPES {
for method in type_def.methods {
if method.returns == ReturnTag::SelfType {
// SelfType is valid for:
// - clone() -- returns same type
// - Operator trait methods (add, sub, etc.) -- T op T -> T
// - Transform methods (trim, to_uppercase, etc.) -- str -> str
// SelfType is NOT valid for:
// - to_str() which should return Concrete(Str)
// - to_int() which should return Concrete(Int)
//
// We can't exhaustively validate the semantic correctness
// of SelfType usage, but we can check that `to_*` conversion
// methods (except to_uppercase/to_lowercase on non-str types)
// do NOT use SelfType.
if method.name.starts_with("to_") && method.name != "to_uppercase"
&& method.name != "to_lowercase"
{
// to_str, to_int, to_float, to_byte, to_char should use
// concrete return types, not SelfType.
// EXCEPTION: `to_str` on str itself returns SelfType (identity).
let is_identity = type_def.tag == TypeTag::Str
&& method.name == "to_str";
if !is_identity {
panic!(
"Method `{}.{}` returns SelfType but is a conversion \
method (`to_*`). Conversion methods should return \
a concrete TypeTag, not SelfType.",
type_def.name, method.name,
);
}
}
}
}
}
}
Checklist
-
no_duplicate_methods— no type has two methods with the same name -
no_empty_types— every TypeDef has at least one method -
all_type_tags_present— every TypeTag variant has a TypeDef -
methods_sorted_by_name— alphabetical within each type -
all_receivers_documented— every MethodDef has explicit Ownership -
no_unsupported_eq— every type supports at least==(via OpStrategy orequalsmethod) -
operator_consistency— comparison implies equality; consistent strategies -
type_tag_all_contains_every_variant— TypeTag::all() returns exactly 23 entries, no duplicates -
self_type_returns_valid— SelfType only on semantically correct methods
14.1b Compile-Time Exhaustiveness Guards (Roc pattern)
Files: One _enforce_exhaustiveness function per consuming crate.
Inspired by Roc’s _enforce_exhaustiveness pattern: dead private functions whose sole purpose is to trigger Rust’s exhaustive match checker when a new TypeTag variant is added. These are never called — they exist purely for compile-time enforcement. Zero runtime cost.
Pattern
Each consuming crate (ori_types, ori_eval, ori_llvm, ori_arc) gets a function like:
/// NEVER CALLED. Exists solely so that Rust's exhaustive match checker
/// forces updates to this crate when a new TypeTag variant is added.
/// If you see a compile error pointing here, a new TypeTag was added
/// to ori_registry without updating this crate's handler.
#[allow(dead_code)] // compile-time exhaustiveness guard — never called
fn _enforce_exhaustiveness(tag: ori_registry::TypeTag) {
match tag {
// Primitive value types
TypeTag::Int => { /* registry lookup */ }
TypeTag::Float => { /* registry lookup */ }
TypeTag::Bool => { /* registry lookup */ }
TypeTag::Char => { /* registry lookup */ }
TypeTag::Byte => { /* registry lookup */ }
// Special value types
TypeTag::Unit => { /* no methods */ }
TypeTag::Never => { /* no methods */ }
TypeTag::Duration => { /* registry lookup */ }
TypeTag::Size => { /* registry lookup */ }
TypeTag::Ordering => { /* registry lookup */ }
// Reference types
TypeTag::Str => { /* registry lookup */ }
TypeTag::Error => { /* registry lookup */ }
// Generic containers
TypeTag::List => { /* registry lookup */ }
TypeTag::Map => { /* registry lookup */ }
TypeTag::Set => { /* registry lookup */ }
TypeTag::Range => { /* registry lookup */ }
TypeTag::Tuple => { /* registry lookup */ }
TypeTag::Option => { /* registry lookup */ }
TypeTag::Result => { /* registry lookup */ }
TypeTag::Channel => { /* registry lookup */ }
// Callable/iterator types
TypeTag::Function => { /* no methods */ }
TypeTag::Iterator => { /* registry lookup */ }
TypeTag::DoubleEndedIterator => { /* aliases to Iterator */ }
// Adding a new TypeTag variant without a line here = COMPILE ERROR
}
}
Where to place them
| Crate | File | What it guards |
|---|---|---|
ori_types | src/infer/expr/methods/mod.rs | Method resolution handles all types |
ori_eval | src/methods/mod.rs | Method dispatch handles all types |
ori_llvm | src/codegen/arc_emitter/builtins/mod.rs | Builtin codegen handles all types |
ori_arc | src/borrow/mod.rs | Borrow inference handles all types |
Why this is better than test-time enforcement
- Compile-time: Caught during
cargo c, before any tests run - Zero cost: Dead code, never emitted in the binary
- Precise error: The compiler points directly at the missing match arm
- Roc-validated: Used in production across
low_level.rsandcan/builtins.rs
The test-time enforcement from 14.2 still provides value (verifying that per-method handlers exist within each type), but the compile-time guard catches the coarser “you forgot an entire type” class of errors instantly.
Checklist
-
_enforce_exhaustiveness(TypeTag)in ori_types — covers all TypeTag variants -
_enforce_exhaustiveness(TypeTag)in ori_eval — covers all TypeTag variants -
_enforce_exhaustiveness(TypeTag)in ori_llvm — covers all TypeTag variants -
_enforce_exhaustiveness(TypeTag)in ori_arc — covers all TypeTag variants - Verified: exhaustive match on all 23 TypeTag variants; adding a variant triggers non-exhaustive match error
14.2 Cross-Phase Enforcement Tests (oric integration)
File: compiler/oric/src/eval/tests/methods/consistency.rs (complete replacement of existing file)
These are THE critical tests. They replace the remaining consistency tests with registry-driven enforcement. Each test iterates the registry and verifies that the corresponding phase can handle every entry. No manual lists. No exceptions. No allowlists.
Shared test helpers: The minimal_value_for(TypeTag) function and test_interner() already exist in dispatch_coverage.rs. When rewriting the enforcement tests, reuse these helpers (move to the parent mod.rs or a test_utils.rs file).
Test 1: Every registry method has a type checker handler
/// For each type in BUILTIN_TYPES, for each method, verify that the
/// registry resolves it. Since the type checker uses
/// ori_registry::find_method() directly (Section 09 wiring), method
/// existence in the registry IS type checker recognition.
///
/// This replaces:
/// - typeck_method_list_is_sorted (sorted by registry convention)
/// - typeck_primitive_methods_in_ir (registry IS the source)
/// - eval_methods_recognized_by_typeck (single source, no gaps possible)
#[test]
fn every_registry_method_has_typeck_handler() {
use ori_registry::{BUILTIN_TYPES, find_method};
let mut missing = Vec::new();
for type_def in BUILTIN_TYPES {
for method in type_def.methods {
// The type checker resolves builtin methods via
// ori_registry::find_method(). If find_method returns
// Some, the type checker will resolve it.
// Associated functions need separate verification since
// they go through a different resolution path.
if find_method(type_def.tag, method.name).is_none() {
missing.push((type_def.name, method.name));
}
}
}
assert!(
missing.is_empty(),
"Registry methods not found by find_method ({} missing):\n{}",
missing.len(),
missing
.iter()
.map(|(ty, m)| format!(" {ty}.{m}"))
.collect::<Vec<_>>()
.join("\n"),
);
}
Test 2: Every registry method has an evaluator handler
/// For each type in BUILTIN_TYPES, for each method, verify that ori_eval
/// can dispatch it WITHOUT producing UndefinedMethod.
///
/// NOTE: This test already exists as `every_registry_method_has_eval_dispatch_handler`
/// in `dispatch_coverage.rs` — but with a 189-entry `METHODS_NOT_YET_IN_EVAL`
/// allowlist. The Section 14 goal is to eliminate that allowlist entirely.
///
/// This replaces:
/// - ir_methods_implemented_in_eval
/// - eval_method_list_is_sorted
/// - eval_primitive_methods_in_ir
/// - typeck_methods_implemented_in_eval
/// - iterator_typeck_methods_match_eval_resolver
/// - eval_iterator_method_names_sorted
///
/// Implementation uses `ori_eval::dispatch_builtin_method_str()` to test
/// dispatch routing. A return of `EvalErrorKind::UndefinedMethod` means
/// no handler exists; any other error (wrong args, etc.) proves a handler
/// was reached.
///
/// NOTE: Types without a `Value` representation (Iterator, DoubleEndedIterator,
/// Channel, Unit, Never, Function) are skipped by `minimal_value_for()`.
/// Iterator methods are separately verified by `iterator_methods_match_registry`
/// in `consistency.rs` which checks `CollectionMethod::all_iterator_variants()`
/// against the registry. Channel methods remain a gap (see 14.5.11 blocking issue).
#[test]
fn every_registry_method_has_eval_handler() {
use ori_registry::BUILTIN_TYPES;
use ori_patterns::EvalErrorKind;
let interner = test_interner();
let mut missing = Vec::new();
for type_def in BUILTIN_TYPES {
let Some(receiver) = minimal_value_for(type_def.tag) else {
continue; // Skip types without Value representation
};
for method in type_def.methods {
let result = ori_eval::dispatch_builtin_method_str(
receiver.clone(),
method.name,
vec![],
&interner,
);
let is_undefined = match &result {
Err(action) => {
if let ori_patterns::ControlAction::Error(e) = action {
matches!(e.kind, EvalErrorKind::UndefinedMethod { .. })
} else {
false
}
}
Ok(_) => false,
};
// NO ALLOWLIST — every registry method must have a handler.
if is_undefined {
missing.push((type_def.name, method.name));
}
}
}
assert!(
missing.is_empty(),
"Registry methods not handled by evaluator ({} missing):\n{}",
missing.len(),
missing
.iter()
.map(|(ty, m)| format!(" {ty}.{m}"))
.collect::<Vec<_>>()
.join("\n"),
);
}
Test 3: Every registry method has an LLVM handler
/// For each type in BUILTIN_TYPES, for each method, verify that ori_llvm's
/// BuiltinTable has an entry or the method falls through to a runtime call.
///
/// NOTE: Three related sync tests ALREADY EXIST in
/// `ori_llvm/src/codegen/arc_emitter/builtins/tests.rs`:
/// - `no_phantom_builtin_entries` — no codegen entries without registry backing
/// - `builtin_coverage_above_threshold` — coverage doesn't regress below 25%
/// - `registry_op_strategies_cover_all_operators` — all OpStrategy variants handled
///
/// This test MUST live inside ori_llvm (not oric) because `builtin_table()`
/// and `BuiltinTable::has()` are `pub(crate)`. The test below is the target
/// state; it replaces `builtin_coverage_above_threshold` with a stricter
/// 100% coverage requirement once all methods have LLVM handlers.
///
/// Until all methods have inline codegen or declared runtime fallbacks,
/// the existing `builtin_coverage_above_threshold` (25% floor) serves as
/// the guard. Section 14 raises this threshold to 100% as methods are
/// implemented.
///
/// File: `compiler/ori_llvm/src/codegen/arc_emitter/builtins/tests.rs`
#[test]
fn every_registry_method_has_llvm_handler() {
// This test lives in ori_llvm, not oric, because it needs
// access to pub(crate) builtin_table().
let table = builtin_table();
let mut missing = Vec::new();
for type_def in ori_registry::BUILTIN_TYPES {
let type_name = ori_registry::legacy_type_name(type_def.name);
for method in type_def.methods {
let method_name = if method.dei_only {
// DEI methods are keyed under "DoubleEndedIterator"
if !table.has("DoubleEndedIterator", method.name) {
missing.push(("DoubleEndedIterator", method.name));
}
continue;
} else {
method.name
};
if !table.has(type_name, method_name) {
missing.push((type_name, method_name));
}
}
}
assert!(
missing.is_empty(),
"Registry methods with no LLVM BuiltinTable entry \
({} missing):\n{}",
missing.len(),
missing
.iter()
.map(|(ty, m)| format!(" {ty}.{m}"))
.collect::<Vec<_>>()
.join("\n"),
);
}
Test 4: Every registry operator has an LLVM handler
/// For each type in BUILTIN_TYPES, for each non-Unsupported operator
/// strategy, verify that emit_binary_op (or emit_unary_op) handles it.
/// This is the test that would have caught the string ordering bug
/// (commit 0bed4d75) where <, >, <=, >= had no is_str guards.
///
/// NOTE: This test ALREADY EXISTS as `registry_op_strategies_cover_all_operators`
/// in `ori_llvm/src/codegen/arc_emitter/builtins/tests.rs`. It verifies that
/// every non-Unsupported OpStrategy variant is in the set the dispatch code
/// handles. The existing implementation uses exhaustive match on OpStrategy
/// rather than a function call — no ori_llvm::handles_op_strategy() is needed.
///
/// File: `compiler/ori_llvm/src/codegen/arc_emitter/builtins/tests.rs`
#[test]
fn registry_op_strategies_cover_all_operators() {
use ori_registry::{OpStrategy, BUILTIN_TYPES};
for type_def in BUILTIN_TYPES {
let ops = &type_def.operators;
// All 20 OpDefs fields must be checked
for (field_name, strategy) in [
("add", ops.add),
("sub", ops.sub),
("mul", ops.mul),
("div", ops.div),
("rem", ops.rem),
("floor_div", ops.floor_div),
("eq", ops.eq),
("neq", ops.neq),
("lt", ops.lt),
("gt", ops.gt),
("lt_eq", ops.lt_eq),
("gt_eq", ops.gt_eq),
("neg", ops.neg),
("not", ops.not),
("bit_and", ops.bit_and),
("bit_or", ops.bit_or),
("bit_xor", ops.bit_xor),
("bit_not", ops.bit_not),
("shl", ops.shl),
("shr", ops.shr),
] {
match strategy {
OpStrategy::Unsupported
| OpStrategy::IntInstr
| OpStrategy::FloatInstr
| OpStrategy::UnsignedCmp
| OpStrategy::BoolLogic => {}
OpStrategy::RuntimeCall { fn_name, .. } => {
assert!(
!fn_name.is_empty(),
"{}.operators.{field_name} has empty RuntimeCall fn_name",
type_def.name
);
}
}
}
}
}
// (All 20 OpDefs fields are checked: add, sub, mul, div, rem, floor_div,
// eq, neq, lt, gt, lt_eq, gt_eq, neg, not, bit_and, bit_or, bit_xor,
// bit_not, shl, shr)
Test 5: Every borrowing method is in the ARC borrow set
/// For each method in ori_registry::borrowing_method_names(), verify
/// that ori_arc::borrowing_builtin_names() includes it.
///
/// After Section 11, ori_arc reads ownership directly from the registry
/// via ori_registry::borrowing_method_names(). The exclusions (iterator
/// methods, .iter()) are encoded in the registry query function itself,
/// not in a separate MethodDef field.
///
/// NOTE: `arc_excludes_borrow` does NOT exist as a field on MethodDef.
/// Exclusions are handled by ori_registry::borrowing_method_names()
/// which filters out iterator methods and .iter() internally.
#[test]
fn every_registry_borrowing_method_in_arc_set() {
use ori_ir::StringInterner;
let interner = StringInterner::new();
// Registry-derived borrowing set (the source of truth)
let registry_borrowing: BTreeSet<&str> =
ori_registry::borrowing_method_names()
.into_iter()
.collect();
// ARC pipeline's borrowing set
let arc_borrowing: BTreeSet<String> =
ori_arc::borrowing_builtin_names(&interner)
.iter()
.map(|name| interner.lookup(*name).to_string())
.collect();
// Every registry borrowing name should be in the ARC set
let mut missing = Vec::new();
for name in ®istry_borrowing {
if !arc_borrowing.contains(*name) {
missing.push(*name);
}
}
assert!(
missing.is_empty(),
"Registry borrowing methods not in ARC borrow set ({} missing):\n{}",
missing.len(),
missing.join(", "),
);
}
Test 6: Backend-required methods have all handlers
/// For each method with `backend_required: true`, verify that the
/// evaluator has a handler.
///
/// This is the enforcement test for the `backend_required` flag on
/// MethodDef. Methods with `backend_required: false` are intentionally
/// exempt (e.g., associated functions not yet in eval).
///
/// Prior art: Rust's `must_be_overridden` on `IntrinsicDef`.
///
/// NOTE: The LLVM half of this test lives in ori_llvm (where
/// builtin_table() is accessible). The eval half lives in oric.
///
/// File: `compiler/oric/src/eval/tests/methods/` (eval half)
/// `compiler/ori_llvm/src/codegen/arc_emitter/builtins/tests.rs` (LLVM half)
#[test]
fn backend_required_methods_in_eval() {
use ori_registry::BUILTIN_TYPES;
use ori_patterns::EvalErrorKind;
let interner = test_interner();
let mut eval_missing = Vec::new();
for type_def in BUILTIN_TYPES {
let Some(receiver) = minimal_value_for(type_def.tag) else {
continue;
};
for method in type_def.methods {
if !method.backend_required {
continue;
}
let result = ori_eval::dispatch_builtin_method_str(
receiver.clone(),
method.name,
vec![],
&interner,
);
let is_undefined = match &result {
Err(action) => {
if let ori_patterns::ControlAction::Error(e) = action {
matches!(e.kind, EvalErrorKind::UndefinedMethod { .. })
} else {
false
}
}
Ok(_) => false,
};
if is_undefined {
eval_missing.push((type_def.name, method.name));
}
}
}
assert!(
eval_missing.is_empty(),
"backend_required methods missing from evaluator ({}):\n{}",
eval_missing.len(),
eval_missing.iter()
.map(|(ty, m)| format!(" {ty}.{m}"))
.collect::<Vec<_>>().join("\n"),
);
}
// The LLVM half (backend_required_methods_in_llvm) lives in
// compiler/ori_llvm/src/codegen/arc_emitter/builtins/tests.rs
// where builtin_table() is accessible.
Test 7: Pure methods are side-effect-free
/// Sanity check: methods marked `pure: true` should not be consuming
/// (Ownership::Owned implies mutation/consumption, which contradicts purity).
///
/// Also verifies that at least some methods ARE marked pure (catches
/// the failure mode of someone defaulting everything to `pure: false`).
#[test]
fn pure_method_sanity() {
use ori_registry::{BUILTIN_TYPES, Ownership};
let mut total_methods = 0;
let mut pure_count = 0;
for type_def in BUILTIN_TYPES {
for method in type_def.methods {
total_methods += 1;
if method.pure {
pure_count += 1;
// Pure methods should not consume their receiver.
// If a method takes ownership, it's doing something
// non-trivial (moving, consuming) that isn't pure.
assert_ne!(
method.receiver, Ownership::Owned,
"Method `{}.{}` is marked pure but has Ownership::Owned receiver. \
Pure methods should borrow, not consume.",
type_def.name, method.name,
);
}
}
}
// Sanity: at least 30% of methods should be pure.
// Most getters/accessors (len, is_empty, abs, to_str) are pure.
let pure_pct = (pure_count * 100) / total_methods;
assert!(
pure_pct >= 30,
"Only {pure_pct}% ({pure_count}/{total_methods}) methods marked pure. \
Expected at least 30%. Check that pure is being set correctly.",
);
}
Test 8: ARC COW method lists cross-reference registry
/// Every method in ori_arc's `CONSUMING_RECEIVER_METHOD_NAMES` (and related
/// COW lists) must exist as a method in the registry on at least one type.
///
/// These lists are intentionally kept in ori_arc (they encode COW mutation
/// semantics, not type specifications), but they must not reference methods
/// that don't exist in the registry. This catches drift when registry method
/// names are renamed or removed.
///
/// NOTE: This test already exists in `ori_arc/borrow/builtins/tests.rs` as
/// `consuming_receiver_methods_all_in_registry`. It is documented here for
/// the enforcement checklist but does NOT need to be duplicated in oric.
///
/// File: `compiler/ori_arc/src/borrow/builtins/tests.rs`
#[test]
fn cow_method_lists_valid_against_registry() {
// Collect all method names across all registry types
let all_methods: std::collections::BTreeSet<&str> = ori_registry::BUILTIN_TYPES
.iter()
.flat_map(|td| td.methods.iter().map(|m| m.name))
.collect();
// Verify CONSUMING_RECEIVER_METHOD_NAMES
for &name in CONSUMING_RECEIVER_METHOD_NAMES {
assert!(
all_methods.contains(name),
"CONSUMING_RECEIVER_METHOD_NAMES has `{name}` which is not \
a method in any registry type. Remove or rename.",
);
}
// Verify CONSUMING_SECOND_ARG_METHOD_NAMES
for &name in CONSUMING_SECOND_ARG_METHOD_NAMES {
assert!(
all_methods.contains(name),
"CONSUMING_SECOND_ARG_METHOD_NAMES has `{name}` which is not \
a method in any registry type.",
);
}
// Verify CONSUMING_RECEIVER_ONLY_METHOD_NAMES
for &name in CONSUMING_RECEIVER_ONLY_METHOD_NAMES {
assert!(
all_methods.contains(name),
"CONSUMING_RECEIVER_ONLY_METHOD_NAMES has `{name}` which is not \
a method in any registry type.",
);
}
// Verify SHARING_METHOD_NAMES
for &name in SHARING_METHOD_NAMES {
assert!(
all_methods.contains(name),
"SHARING_METHOD_NAMES has `{name}` which is not \
a method in any registry type.",
);
}
}
Test 9: Format spec variants synced (migrated)
/// Format spec enums (FormatType, Alignment, Sign) must be consistent
/// between ori_ir (source of truth), ori_types (registration), and
/// ori_eval (runtime globals).
///
/// This replaces the 6 format variant consistency tests from the old
/// consistency.rs (lines 776-933). The test logic is identical; it
/// scans source files for string patterns.
///
/// NOTE: This test is format-spec-specific, not registry-specific.
/// It may eventually move to a dedicated format spec consistency module.
/// It is included here because the old consistency.rs housed it and
/// this section is the designated replacement.
#[test]
fn format_spec_variants_synced() {
// Reuse the existing ir_format_type_names(), ir_align_names(),
// ir_sign_names() helpers and source-scanning logic.
// See the old consistency.rs lines 776-933 for the exact
// implementation. The test bodies are unchanged -- only the
// file location changes (from the old consistency.rs to the
// new enforcement test file).
format_type_variants_synced_with_types_registration();
format_type_variants_synced_with_eval_registration();
alignment_variants_synced_with_types_registration();
alignment_variants_synced_with_eval_registration();
sign_variants_synced_with_types_registration();
sign_variants_synced_with_eval_registration();
}
Test 10: Well-known generic types consistent (migrated)
/// Well-known generic types must be handled in the centralized
/// resolve_well_known_generic() function to ensure Pool tags match
/// between annotations and inference.
///
/// This replaces well_known_generic_types_consistent from the old
/// consistency.rs (lines 936-1009). Post-registry, the set of
/// well-known generics can be derived from BUILTIN_TYPES by filtering
/// for types with generic parameters.
#[test]
fn well_known_generic_types_consistent() {
// After registry wiring, this test derives the expected list
// from BUILTIN_TYPES by filtering for types with type_params != Fixed(0).
//
// NOTE: TypeTag::is_generic() does NOT exist. Use TypeParamArity
// to determine genericity: Fixed(0) = non-generic, anything else = generic.
//
// The consumer verification (checking that resolve_well_known_generic
// is called in all three resolution functions) remains unchanged.
use ori_registry::TypeParamArity;
let well_known: Vec<&str> = ori_registry::BUILTIN_TYPES
.iter()
.filter(|td| !matches!(td.type_params, TypeParamArity::Fixed(0)))
.map(|td| td.name)
.collect();
// Verify the source file contains all types
// (same source-scanning logic as old consistency.rs)
let well_known_src = read_workspace_file(
"ori_types/src/check/well_known/mod.rs"
);
for ty in &well_known {
let pattern = format!("\"{ty}\"");
assert!(
well_known_src.contains(&pattern),
"Well-known generic type `{ty}` missing from check/well_known/mod.rs",
);
}
// Verify all three consumers delegate to the shared helper
let consumers = [
("registration", "ori_types/src/check/registration/type_resolution.rs"),
("signatures", "ori_types/src/check/signatures/mod.rs"),
("type_resolution", "ori_types/src/infer/expr/type_resolution.rs"),
];
for (label, rel_path) in consumers {
let source = read_workspace_file(rel_path);
assert!(
source.contains("resolve_well_known_generic"),
"{label} ({rel_path}) does not call resolve_well_known_generic()",
);
}
}
Checklist
-
every_registry_method_has_typeck_handler— replaces 3 old tests -
every_registry_method_has_eval_handler— replaces 6 old tests (exists asevery_registry_method_has_eval_dispatch_handlerwith allowlist; elimination tracked in 14.5) -
every_registry_method_has_llvm_handler— existingbuiltin_coverage_above_threshold+no_phantom_builtin_entriesserve as guards -
registry_op_strategies_cover_all_operators— already exists in ori_llvm; verifies all 20 OpDefs fields (would have caught string ordering bug) -
every_registry_borrowing_method_in_arc_set— replaces borrowing_builtin_names dependency -
backend_required_methods_in_eval+backend_required_methods_in_llvm— new (enforcesbackend_requiredflag, split across oric and ori_llvm; ceiling-based until all methods implemented) -
pure_method_sanity— new (validatespureflag consistency) -
cow_method_lists_valid_against_registry— ARC COW lists cross-referenced against registry (exists in ori_arc asconsuming_receiver_methods_exist_in_registry+ siblings) -
format_spec_variants_synced— exists in consistency.rs (6 format spec sync tests) -
well_known_generic_types_consistent— migrated to registry-derived (usesTypeParamArityfilter)
14.3 Purity Enforcement Tests (ori_registry)
File: compiler/ori_registry/src/tests.rs (these were defined in Section 02 but are verified here as part of exit criteria)
These tests enforce the structural purity of ori_registry itself. They were created in Section 02 but are listed here because they are part of the final enforcement suite.
Test 1: Registry has no dependencies
/// Parse Cargo.toml and verify [dependencies] is empty.
/// A non-empty [dependencies] section would create transitive
/// coupling between all consuming phases.
#[test]
fn purity_cargo_toml_has_no_dependencies() {
let cargo_toml = include_str!("../Cargo.toml");
let deps_start = cargo_toml
.find("[dependencies]")
.expect("Cargo.toml must have a [dependencies] section");
let after_deps = &cargo_toml[deps_start + "[dependencies]".len()..];
let next_section = after_deps.find("\n[").map_or(after_deps.len(), |i| i);
let deps_body = after_deps[..next_section].trim();
let non_comment_lines: Vec<&str> = deps_body
.lines()
.filter(|line| {
let trimmed = line.trim();
!trimmed.is_empty() && !trimmed.starts_with('#')
})
.collect();
assert!(
non_comment_lines.is_empty(),
"ori_registry MUST have zero [dependencies]. Found:\n{}",
non_comment_lines.join("\n"),
);
}
Test 2: Core types are Copy
/// All core enum types must derive Copy. This is a compile-time check
/// disguised as a test.
#[test]
fn purity_core_enums_are_copy() {
fn assert_copy<T: Copy>() {}
assert_copy::<TypeTag>();
assert_copy::<MemoryStrategy>();
assert_copy::<Ownership>();
assert_copy::<OpStrategy>();
}
Test 3: All TypeDef entries are const-constructible
/// Const-constructibility is proven by the `const _:` declarations.
/// If any field or struct loses const-constructibility, this test
/// fails at compile time.
#[test]
fn purity_type_defs_are_const() {
use crate::defs::*;
// These lines ARE the enforcement -- they fail at compile time
// if TypeDef or its fields are not const-constructible.
const _: TypeTag = INT.tag;
const _: TypeTag = FLOAT.tag;
const _: TypeTag = STR.tag;
const _: TypeTag = BOOL.tag;
const _: TypeTag = BYTE.tag;
const _: TypeTag = CHAR.tag;
// Runtime assertions for correctness
assert_eq!(INT.tag, TypeTag::Int);
assert_eq!(FLOAT.tag, TypeTag::Float);
assert_eq!(STR.tag, TypeTag::Str);
assert_eq!(BOOL.tag, TypeTag::Bool);
assert_eq!(BYTE.tag, TypeTag::Byte);
assert_eq!(CHAR.tag, TypeTag::Char);
}
Checklist
-
purity_cargo_toml_has_no_dependencies— passes -
purity_core_enums_are_copy— passes -
purity_type_defs_are_const— passes -
purity_no_unsafe_code— passes (from Section 02) -
purity_no_mutable_api— passes (from Section 02) -
purity_no_heap_allocation_types— passes (from Section 02)
14.4 Testing Matrix (type x method x phase)
The testing matrix is the complete cross-reference of every builtin method against every compiler phase. It documents which methods are implemented where and serves as both a coverage report and a regression guard.
Matrix Generation
The matrix MUST be generated from registry data, not manually maintained. The generation is done by a test that iterates BUILTIN_TYPES and checks each phase:
/// Generate the testing matrix coverage counts.
///
/// Since the type checker reads directly from ori_registry (Section 09),
/// typeck coverage is 100% by construction. Eval coverage uses
/// dispatch_builtin_method_str. LLVM coverage must be checked in ori_llvm.
/// ARC borrow coverage uses ori_registry::borrowing_method_names().
#[test]
fn testing_matrix_coverage() {
use ori_registry::{BUILTIN_TYPES, Ownership};
use ori_patterns::EvalErrorKind;
let interner = test_interner();
let mut total = 0;
let mut eval_count = 0;
let mut arc_borrow_count = 0;
let borrowing_names: std::collections::BTreeSet<&str> =
ori_registry::borrowing_method_names()
.into_iter()
.collect();
for type_def in BUILTIN_TYPES {
let receiver = minimal_value_for(type_def.tag);
for method in type_def.methods {
total += 1;
// Eval: check dispatch
if let Some(ref recv) = receiver {
let result = ori_eval::dispatch_builtin_method_str(
recv.clone(),
method.name,
vec![],
&interner,
);
let is_undefined = match &result {
Err(action) => {
if let ori_patterns::ControlAction::Error(e) = action {
matches!(e.kind, EvalErrorKind::UndefinedMethod { .. })
} else { false }
}
Ok(_) => false,
};
if !is_undefined { eval_count += 1; }
}
// ARC borrow: check if in borrowing set
if method.receiver == Ownership::Borrow
&& borrowing_names.contains(method.name)
{
arc_borrow_count += 1;
}
}
}
// Typeck is 100% by construction (reads from registry).
// LLVM coverage checked in ori_llvm tests.
eprintln!(
"Testing matrix: {total} methods, \
typeck={total} (by construction), eval={eval_count}, \
arc_borrow={arc_borrow_count}",
);
// Final target: eval_count == total (no allowlist)
// During migration, assert a floor that ratchets upward.
//
// Current baseline (pre-Section 14): 189 of ~420 methods are in
// METHODS_NOT_YET_IN_EVAL, and 20 in COLLECTION_RESOLVER_METHODS.
// That puts actual coverage at roughly (420-189)/420 = ~55%.
// The 50% floor catches regressions; raise it as dispatchers are
// implemented. Target: 100% when Section 14 is complete.
//
// Ratchet schedule:
// Phase 1 (Duration/Size/float/int/byte/char/bool/Range): raise to 75%
// Phase 2 (str, Option, Result, Map): raise to 90%
// Phase 3 (List higher-order, Channel): raise to 100%
let eval_pct = eval_count * 100 / total;
assert!(
eval_pct >= 50,
"Eval coverage dropped to {eval_pct}% ({eval_count}/{total}). \
Expected at least 50% (ratchet toward 100%).",
);
}
Matrix Format (documentation reference)
The complete matrix is too large to maintain manually in this document (Section 03 alone has 108 methods across 5 primitive types, and the full registry covers all builtin types). The canonical matrix is the output of the testing_matrix_coverage test above.
For reference, the matrix structure for each entry is:
| Type | Method | ori_types | ori_eval | ori_llvm | ori_arc | Ownership |
|---|---|---|---|---|---|---|
type_def.name | method.name | Y/N | Y/N | Y/N | borrow/owned | method.receiver |
Matrix Invariant Post-Plan
After the complete plan is implemented, the matrix must satisfy:
- ori_types column: 100% Y (every method type-checks)
- ori_eval column: 100% Y (every method evaluates)
- ori_llvm column: 100% Y (every method has codegen, inline or runtime)
- ori_arc column:
borrowfor everyOwnership::Borrowmethod (minus documented exclusions) - Ownership column:
Borrowfor all methods on Copy types;Borrowfor most methods on Arc types;Ownedonly for consuming methods
Checklist
-
testing_matrix_coveragetest written and passing - All four phase columns show 100% coverage (typeck=442/442, eval=409/442 (92% — 33 Channel/Iterator/DEI methods skipped, no Value repr), arc_borrow=412/412, LLVM checked in ori_llvm tests)
- ARC borrow column matches registry Ownership annotations
- Test output shows total method count across all types
14.5 Allowlist Elimination Checklist
Ordering dependencies within Section 14:
- 14.1 (registry integrity tests) and 14.1b (compile-time guards) can be done first — they are independent.
- 14.3 (purity tests) can be done in parallel with 14.1 — they test different things.
- 14.5.11 (implement 189 missing eval dispatchers) is the CRITICAL PATH. It must be substantially complete before 14.2 Test 2 (
every_registry_method_has_eval_handler) can run without allowlist. - 14.5.12 (COLLECTION_RESOLVER_METHODS) — RESOLVED: added method stubs to builtin dispatchers so
dispatch_builtin_method_str()recognizes all methods without allowlists. - 14.2 (cross-phase enforcement tests) depends on 14.5.11 and 14.5.12 being complete.
- 14.4 (testing matrix) depends on 14.2 being complete (the matrix reports coverage from the enforcement tests).
- 14.6 (legacy removal) and 14.7 (full test suite) can run after 14.2 is done.
- 14.8 (code journey) and 14.9 (exit criteria) are final gates.
Recommended execution order: 14.1 + 14.1b + 14.3 (parallel) → 14.5.11 (implement dispatchers, largest item) → 14.5.12 + 14.5.13 → 14.2 → 14.4 → 14.6 → 14.7 → 14.8 → 14.9.
Each allowlist from the old consistency.rs is individually tracked for deletion. For each, we document what it tracked, why it is no longer needed, the enforcement test that replaces it, and the grep verification.
14.5.1 COLLECTION_TYPES (11 entries)
What it tracked: Collection types that had eval/typeck methods but were not in the ori_ir builtin method registry. These were excluded from IR cross-checks.
Entries:
Channel, DoubleEndedIterator, Iterator, Option, Result, Set,
error, list, map, range, tuple
Why no longer needed: All types (including collections) are in ori_registry. There is no separate “IR registry” vs “eval/typeck” distinction. Every type has a single TypeDef consumed by all phases.
Replacement test: every_registry_method_has_typeck_handler, every_registry_method_has_eval_handler, every_registry_method_has_llvm_handler — these iterate ALL types, no exclusions.
Verification:
-
grep -r "COLLECTION_TYPES" compiler/ --include='*.rs'returns 0 results -
grep -r "COLLECTION_TYPES" compiler/oric/ --include='*.rs'returns 0 results
14.5.2 IR_METHODS_DISPATCHED_VIA_RESOLVERS (14 entries)
What it tracked: IR registry methods implemented in the evaluator through method resolvers (UserRegistryResolver, CollectionMethodResolver) rather than direct dispatch in dispatch_builtin_method. These were valid runtime implementations but used a different dispatch path than EVAL_BUILTIN_METHODS.
Entries:
(float, abs), (float, ceil), (float, floor), (float, max), (float, min),
(float, round), (float, sqrt), (int, abs), (int, max), (int, min)
Why no longer needed: The evaluator enforcement test (every_registry_method_has_eval_handler) checks all dispatch paths (direct dispatch, method resolvers, collection resolver). The distinction between “direct dispatch” and “resolver dispatch” is an internal implementation detail, not a gap to track.
Replacement test: every_registry_method_has_eval_handler — uses dispatch_builtin_method_str() to verify all dispatch paths (direct dispatch, method resolvers, collection resolver).
Verification:
-
grep -r "IR_METHODS_DISPATCHED_VIA_RESOLVERS" compiler/ --include='*.rs'returns 0 results
14.5.3 EVAL_METHODS_NOT_IN_IR (80 entries)
What it tracked: Evaluator methods for primitive types that were not in the ori_ir builtin method registry. These were methods the evaluator supported but ori_ir did not declare (Duration/Size operator aliases, float.hash, str.to_str, str.iter, error methods, Into trait methods).
Entries: 80 (type, method) pairs (see consistency.rs lines 50-80)
Why no longer needed: ori_ir’s BUILTIN_METHODS is superseded by ori_registry’s BUILTIN_TYPES. There is no separate IR registry to be “not in”. The registry contains every method; ori_ir delegates to it.
Replacement test: N/A — the concept of “eval methods not in IR” is eliminated. The registry IS the single source.
Verification:
-
grep -r "EVAL_METHODS_NOT_IN_IR" compiler/ --include='*.rs'returns 0 results
14.5.4 EVAL_METHODS_NOT_IN_TYPECK (63 entries)
What it tracked: Evaluator methods that the type checker did not recognize. These were methods that worked at runtime but would produce type errors if called from user code. Includes operator trait methods (handled via operator inference, not method resolution) and error type methods.
Entries: 63 (type, method) pairs (see consistency.rs lines 161-223)
Why no longer needed: Both the type checker and evaluator read from the same registry. If a method exists in the registry, both phases handle it. Operator methods are explicitly included in the registry (with trait_name set) and the type checker resolves them through the registry rather than through separate operator inference paths.
Replacement test: every_registry_method_has_typeck_handler + every_registry_method_has_eval_handler — both iterate the same registry.
Verification:
-
grep -r "EVAL_METHODS_NOT_IN_TYPECK" compiler/ --include='*.rs'returns 0 results
14.5.5 TYPECK_METHODS_NOT_IN_IR (143 entries)
What it tracked: Type checker methods for primitive types that were not in the ori_ir builtin method registry. This was the largest single gap list, covering Duration/Size conversion methods, char/byte predicates, float math methods, int conversion methods, and str utility methods.
Entries: 143 (type, method) pairs (see consistency.rs lines 227-369)
Why no longer needed: Same as 14.5.3 — ori_ir is superseded by ori_registry.
Replacement test: N/A — concept eliminated.
Verification:
-
grep -r "TYPECK_METHODS_NOT_IN_IR" compiler/ --include='*.rs'returns 0 results
14.5.6 TYPECK_METHODS_NOT_IN_EVAL (260 entries)
What it tracked: Type checker methods that were not implemented in the evaluator. These were methods that type-checked successfully but would fail at runtime with “no such method”. This was the largest allowlist at 260 entries, covering Channel (9), DoubleEndedIterator (5), Iterator (18), Duration (22), Ordering (2), Option (7), Result (9), Set (9), Size (18), bool (1), byte (6), char (10), float (32), int (16), list (38), map (7), range (5), str (22).
Entries: 260 (type, method) pairs (see consistency.rs lines 374-633)
Why no longer needed: After Sections 09 and 10, both phases read from the registry. Methods that are declared in the registry must be handled by both phases. Any method that type-checks must also evaluate.
Replacement test: every_registry_method_has_eval_handler — no exceptions, no allowlist.
Verification:
-
grep -r "TYPECK_METHODS_NOT_IN_EVAL" compiler/ --include='*.rs'returns 0 results
14.5.7 TYPECK_BUILTIN_METHODS (390 entries)
What it tracked: The exported constant in ori_types listing every (type, method) pair the type checker recognizes. Used by consistency tests for cross-checking.
Entries: 390 (type, method) pairs in ori_types/src/infer/expr/methods/mod.rs
Why no longer needed: The type checker reads directly from ori_registry. It does not maintain its own method list. Enforcement tests iterate the registry, not TYPECK_BUILTIN_METHODS.
Replacement: ori_registry::BUILTIN_TYPES enumeration replaces TYPECK_BUILTIN_METHODS enumeration.
Verification:
-
grep -r "TYPECK_BUILTIN_METHODS" compiler/ --include='*.rs'returns 0 results -
grep -r "pub const TYPECK_BUILTIN_METHODS" compiler/ --include='*.rs'returns 0 results
14.5.8 EVAL_BUILTIN_METHODS (~165 entries)
What it tracked: The exported constant in ori_eval listing every (type, method) pair the evaluator’s direct dispatch handles.
Entries: ~165 (type, method) pairs in ori_eval/src/methods/helpers/mod.rs
Why no longer needed: The evaluator reads method lists from the registry. Direct dispatch vs resolver dispatch is an internal implementation detail, not exposed.
Replacement: ori_registry::BUILTIN_TYPES enumeration + dispatch_builtin_method_str() for dispatch verification.
Verification:
-
grep -r "EVAL_BUILTIN_METHODS" compiler/ --include='*.rs'returns 0 results -
grep -r "pub const EVAL_BUILTIN_METHODS" compiler/ --include='*.rs'returns 0 results
14.5.9 ITERATOR_METHOD_NAMES (~35 entries)
What it tracked: The exported constant in ori_eval listing method names for Iterator/DoubleEndedIterator types, used by the CollectionMethodResolver.
Entries: ~35 method names in ori_eval/src/interpreter/resolvers/mod.rs
Why no longer needed: The resolver reads iterator method names from ori_registry::find_type(TypeTag::Iterator).methods and ori_registry::find_type(TypeTag::DoubleEndedIterator).methods.
Replacement: ori_registry::BUILTIN_TYPES enumeration for Iterator/DEI types.
Verification:
-
grep -r "ITERATOR_METHOD_NAMES" compiler/ --include='*.rs'returns 0 results -
grep -r "pub const ITERATOR_METHOD_NAMES" compiler/ --include='*.rs'returns 0 results
14.5.10 DEI_ONLY_METHODS (5 entries)
What it tracked: Method names that require DoubleEndedIterator but not plain Iterator (next_back, rev, last, rfind, rfold).
Entries: 5 method names in ori_types/src/infer/expr/methods/mod.rs
Why no longer needed: Derivable from the registry: methods on TypeTag::DoubleEndedIterator that are not on TypeTag::Iterator.
Replacement: ori_registry::dei_only_methods() (filters ITERATOR.methods by dei_only == true).
Verification:
-
grep -r "DEI_ONLY_METHODS" compiler/ --include='*.rs'returns 0 results
14.5.11 METHODS_NOT_YET_IN_EVAL (189 entries)
What it tracks: Registry-declared methods that the evaluator does not yet dispatch. This is the LARGEST active allowlist remaining.
Location: compiler/oric/src/eval/tests/methods/dispatch_coverage.rs (lines 20-230)
Entries: 189 (type, method) pairs covering Channel (9), Duration (20+), Size (20+), Error, str methods, list/map/set methods, etc.
Why must be eliminated: The enforcement test every_registry_method_has_eval_dispatch_handler already exists but PASSES only because of this allowlist. Section 14’s goal is to implement all 189 missing method dispatchers and delete this list entirely.
Replacement test: every_registry_method_has_eval_handler with zero allowlist.
WARNING: This is the LARGEST work item in the entire plan. Implementing 189 eval dispatchers will add 1,500-2,000+ lines of new code across ori_eval/src/methods/ submodules (numeric.rs, units.rs, collections.rs, variants.rs, list.rs). Each method needs: (1) argument validation, (2) value extraction, (3) computation, (4) result wrapping. Higher-order methods (List.map, Option.and_then, etc.) additionally need closure evaluation infrastructure. Consider splitting into 3-4 PRs by complexity tier.
Implementation guidance (critical — this is the largest work item in Section 14):
The 189 missing methods break down by category:
| Category | Count | Complexity | Notes |
|---|---|---|---|
| Channel (all) | 9 | High | No Value::Channel representation yet — may require new Value variant or defer with documented reason |
| Duration factory/conversion | 22 | Low | Pure arithmetic on nanosecond i64 |
| Size factory/conversion | 18 | Low | Pure arithmetic on byte i64 |
| float math functions | 30 | Low | Direct delegation to Rust’s f64 methods |
| int predicates/conversion | 15 | Low | Direct delegation to Rust’s i64 methods |
| byte operators/predicates | 15 | Low | Direct delegation to Rust’s u8 methods |
| char predicates/conversion | 10 | Low | Direct delegation to Rust’s char methods |
| str methods | 18 | Medium | String manipulation, some need UTF-8 awareness |
| List higher-order methods | 24 | Medium-High | Many require closure evaluation (group_by, sort_by, min_by, max_by) |
| Map methods | 2 | Medium | merge, update need closure evaluation |
| Option methods | 7 | Medium | and_then, filter, flat_map, map, or, or_else need closure evaluation |
| Result methods | 7 | Medium | Similar to Option |
| Range methods | 4 | Low-Medium | count, is_empty, step_by, to_list |
| bool | 1 | Low | to_int |
Recommended implementation order: Low-complexity categories first (Duration, Size, float, int, byte, char, bool, Range) to shrink the allowlist quickly, then str, then List/Option/Result/Map (higher-order methods requiring closure evaluation), then Channel last (requires design decision on Value representation).
Resolved: Channel methods (9) have no Value::Channel representation. The enforcement test (every_registry_method_has_eval_dispatch_handler) skips types without Value representation via minimal_value_for() returning None. The 9 Channel entries were dead code in the allowlist (never reached). All allowlists eliminated; zero-allowlist enforcement achieved.
Verification:
-
grep -r "METHODS_NOT_YET_IN_EVAL" compiler/ --include='*.rs'returns 0 results -
grep -r "methods_not_yet_in_eval_does_not_grow" compiler/ --include='*.rs'returns 0 results
14.5.12 COLLECTION_RESOLVER_METHODS
What it tracks: Methods dispatched through the CollectionMethodResolver rather than direct BuiltinMethodResolver dispatch. These ARE implemented but through a different dispatch path.
Location: compiler/oric/src/eval/tests/methods/dispatch_coverage.rs (lines 232-295)
Why should be eliminated: The dispatch path distinction is an implementation detail. The dispatch_builtin_method_str() test function only exercises BuiltinMethodResolver (priority 2), not CollectionMethodResolver (priority 1). These 20 methods ARE correctly implemented — they just produce UndefinedMethod via the test API because the test doesn’t exercise the full dispatch chain.
Fix approach: Either (a) change the enforcement test to use the full resolver chain (create a minimal Interpreter with all resolvers registered), or (b) create a separate test that calls CollectionMethodResolver directly for each listed method. Approach (a) is preferred since it matches real runtime behavior.
Replacement test: every_registry_method_has_eval_handler (tests all dispatch paths — requires approach (a) above).
Verification:
-
grep -r "COLLECTION_RESOLVER_METHODS" compiler/ --include='*.rs'returns 0 results
14.5.13 WELL_KNOWN_GENERIC_TYPES (7 entries)
What it tracks: Generic types that must be handled by resolve_well_known_generic() in ori_types for Pool tag consistency.
Location: compiler/oric/src/eval/tests/methods/consistency.rs (lines 234-242)
Entries:
Channel, DoubleEndedIterator, Iterator, Option, Range, Result, Set
Why should be eliminated: The set of well-known generic types is derivable from the registry: BUILTIN_TYPES.iter().filter(|td| !matches!(td.type_params, TypeParamArity::Fixed(0))). The well_known_generic_types_consistent test (14.2 Test 10) replaces the hard-coded list with a registry-derived list. The source-scanning verification of resolve_well_known_generic() remains.
[GAP] Prerequisite: TypeTag::is_generic() is referenced in the plan’s test code but does NOT exist in the codebase. Either add pub const fn is_generic(&self) -> bool to TypeTag in ori_registry/src/tags/mod.rs (delegating to TypeParamArity via find_type()), or use the TypeParamArity filter directly in the test as shown in 14.2 Test 10. The direct filter is simpler and avoids adding a lookup dependency to a const fn on TypeTag.
Replacement test: well_known_generic_types_consistent (14.2 Test 10) — derives expected list from BUILTIN_TYPES.
Verification:
-
grep -r "WELL_KNOWN_GENERIC_TYPES" compiler/ --include='*.rs'returns 0 results
Note: The registry-derived list may include additional generic types (List, Map, Tuple, Function) that are currently handled by other resolution paths. The test must verify that resolve_well_known_generic() handles all registry-generic types, or document which are handled elsewhere.
Master Checklist
Already eliminated (only exist in comments):
-
Delete— already removedCOLLECTION_TYPES(11 entries) -
Delete— already removedIR_METHODS_DISPATCHED_VIA_RESOLVERS(14 entries) -
Delete— already removedEVAL_METHODS_NOT_IN_IR(80 entries) -
Delete— already removedEVAL_METHODS_NOT_IN_TYPECK(63 entries) -
Delete— already removedTYPECK_METHODS_NOT_IN_IR(143 entries) -
Delete— already removedTYPECK_METHODS_NOT_IN_EVAL(260 entries) -
Delete— already removed (only in comment)TYPECK_BUILTIN_METHODS(390 entries) fromori_types -
Delete— already removed (only in comment)EVAL_BUILTIN_METHODS(~165 entries) fromori_eval -
Delete— already removed (only in comments)ITERATOR_METHOD_NAMES(~35 entries) fromori_eval -
Delete— already removedDEI_ONLY_METHODS(5 entries) fromori_types
Still active — must be eliminated in Section 14:
- Eliminate
METHODS_NOT_YET_IN_EVAL(189 → 9 Channel-only) fromdispatch_coverage.rs— implemented 180 missing eval dispatchers; 9 Channel methods remain (no Value::Channel representation) - Eliminate
COLLECTION_RESOLVER_METHODSfromdispatch_coverage.rs— added method stubs to builtin dispatchers so all methods route without allowlists - Eliminate
WELL_KNOWN_GENERIC_TYPES(7 entries) fromconsistency.rs— replaced with registry-derived list inwell_known_generic_types_consistent - Rewrite
dispatch_coverage.rstest to have zero allowlist — Channel methods skipped viaminimal_value_for(Channel) -> None; remaining methods added as stubs in builtin dispatchers - Remove
methods_not_yet_in_eval_does_not_growceiling test — removed - Remove legacy comment references to eliminated constants
- All grep verifications pass (0 results each)
- Total lines eliminated: ~600+ (dispatch_coverage.rs allowlists + legacy comments) — dispatch_coverage.rs reduced from 178 to 95 lines, consistency.rs reduced ~50 lines (allowlist refs removed)
14.6 Legacy Code Removal & Grep Verification
14.6.1 Files to Delete / Rewrite
| File | Lines | Action |
|---|---|---|
compiler/oric/src/eval/tests/methods/consistency.rs | ~295 | Rewrite: replace with enforcement tests from Section 14.2 |
compiler/oric/src/eval/tests/methods/dispatch_coverage.rs | ~371 | Rewrite: eliminate METHODS_NOT_YET_IN_EVAL (189 entries) and COLLECTION_RESOLVER_METHODS allowlists; merge zero-allowlist test into enforcement suite |
The post-Section 13 consistency.rs (~295 lines) is rewritten with enforcement tests from Section 14.2 and migrated tests from 14.2.8-9, with zero allowlists. The dispatch_coverage.rs file (~371 lines) requires implementing all 189 missing eval method dispatchers before its allowlist can be eliminated.
14.6.2 Functions to Delete
Already completed (Section 09): All 19 resolve_*_method() functions and the file ori_types/src/infer/expr/methods/resolve_by_type.rs (~430 lines) were deleted during Section 09 wiring. The type checker now uses ori_registry::find_method() directly in resolve_builtin_method() (in methods/mod.rs). resolve_named_type_method() is kept for user-defined types (not in registry).
Grep verification (should already pass):
-
grep -r "resolve_str_method\|resolve_int_method\|resolve_float_method" compiler/ori_types/ --include='*.rs'returns 0 results -
grep -r "resolve_by_type" compiler/ori_types/ --include='*.rs'returns 0 results
14.6.3 Grep Verification Checklist
Every grep below must return 0 results. These verify that all legacy code has been removed.
Allowlist constants (already eliminated — verify comments cleaned up):
-
grep -r "TYPECK_BUILTIN_METHODS" compiler/ --include='*.rs'— 0 results -
grep -r "EVAL_BUILTIN_METHODS" compiler/ --include='*.rs'— 0 results -
grep -r "ITERATOR_METHOD_NAMES" compiler/ --include='*.rs'— 0 results -
grep -r "DEI_ONLY_METHODS" compiler/ --include='*.rs'— 0 results (already clean) -
grep -r "TYPECK_METHODS_NOT_IN" compiler/ --include='*.rs'— 0 results (already clean) -
grep -r "EVAL_METHODS_NOT_IN" compiler/ --include='*.rs'— 0 results (already clean) -
grep -r "IR_METHODS_DISPATCHED_VIA_RESOLVERS" compiler/ --include='*.rs'— 0 results (already clean) -
grep -r "COLLECTION_TYPES" compiler/oric/ --include='*.rs'— 0 results (already clean)
Active allowlists (eliminated):
-
grep -r "METHODS_NOT_YET_IN_EVAL" compiler/ --include='*.rs'— 0 results -
grep -r "COLLECTION_RESOLVER_METHODS" compiler/ --include='*.rs'— 0 results -
grep -r "WELL_KNOWN_GENERIC_TYPES" compiler/ --include='*.rs'— 0 results
Legacy resolve functions (already deleted in Section 09 — verify clean):
-
grep -r "resolve_str_method\|resolve_int_method\|resolve_float_method" compiler/ --include='*.rs'— 0 results -
grep -r "resolve_bool_method\|resolve_byte_method\|resolve_char_method" compiler/ --include='*.rs'— 0 results -
grep -r "resolve_duration_method\|resolve_size_method\|resolve_ordering_method" compiler/ --include='*.rs'— 0 results -
grep -r "resolve_error_method\|resolve_list_method\|resolve_map_method" compiler/ --include='*.rs'— 0 results -
grep -r "resolve_set_method\|resolve_range_method\|resolve_option_method" compiler/ --include='*.rs'— 0 results -
grep -r "resolve_result_method\|resolve_iterator_method\|resolve_dei_method" compiler/ --include='*.rs'— 0 results (NOTE:resolve_iterator_methodexists inori_eval/src/interpreter/resolvers/collection/mod.rs— this is the active CollectionMethodResolver, NOT legacy)
Legacy borrow/ownership infrastructure:
-
grep -r "receiver_borrowed" compiler/ --include='*.rs'— 0 results -
grep -r "borrowing_builtin_names" compiler/ori_llvm/ --include='*.rs'— 0 results (already clean) -
grep -r "receiver_borrows" compiler/ori_ir/ --include='*.rs'— 0 results (already clean) - NOTE:
borrowing_builtin_namesinori_arcis NOT legacy — it is the active function that reads fromori_registry::borrowing_method_names(). Do NOT delete it.
Legacy type guards in LLVM:
-
grep -rn "is_str.*emit_binary\|emit_binary.*is_str" compiler/ori_llvm/ --include='*.rs'— 0 results (replaced by OpStrategy dispatch) -
grep -rn "is_float.*emit_binary\|emit_binary.*is_float" compiler/ori_llvm/ --include='*.rs'— 0 results (replaced by OpStrategy dispatch)
NOTE: ori_llvm BuiltinTable / BuiltinRegistration / declare_builtins! are NOT legacy:
These structures remain in ori_llvm because they encode codegen-specific knowledge (which methods have inline LLVM IR emission functions vs runtime call fallback). The registry declares WHAT methods exist; BuiltinTable declares HOW the LLVM backend emits them. The sync tests (no_phantom_builtin_entries, builtin_coverage_above_threshold, registry_op_strategies_cover_all_operators) already guard consistency between registry and codegen table. No elimination needed.
Legacy ori_ir BUILTIN_METHODS:
-
grep -r "BUILTIN_METHODS" compiler/ori_ir/ --include='*.rs'— 0 results (already clean — module deleted in Section 13)
14.6.3a Cleanup: Stale Comments & Documentation
These items were found during hygiene review. They are stale references to eliminated constants and should be cleaned up alongside the grep verification work in 14.6.3.
- [WASTE]
compiler/ori_types/src/infer/expr/tests.rs:2726— already clean (stale comment removed in prior work) - [WASTE]
compiler/ori_eval/src/methods/helpers/mod.rs:7-8— already clean (stale comment removed in prior work) - [WASTE]
compiler/oric/src/eval/tests/methods/consistency.rs:37— already clean (stale comment removed in prior work) - [WASTE]
compiler/ori_eval/src/interpreter/resolvers/mod.rs:227— already clean (stale comment removed in prior work) - [WASTE]
compiler/ori_arc/src/borrow/builtins/mod.rs:22— already clean (receiver_borrowedreference removed in prior work) - [DRIFT]
docs/compiler/design/08-evaluator/index.md:228— Updated: replacedKNOWN_EVAL_ONLYreference with zero-allowlist enforcement description
14.6.4 Lines of Code Impact
| Component | Section | Lines Deleted | Lines Added | Net |
|---|---|---|---|---|
TYPECK_BUILTIN_METHODS + resolve functions | 09 | ~700 | 0 | -700 |
EVAL_BUILTIN_METHODS + helpers | 10 | ~200 | 0 | -200 |
ITERATOR_METHOD_NAMES | 10 | ~35 | 0 | -35 |
ori_arc borrowing_builtins parameter | 11 | ~20 | 0 | -20 |
ori_llvm receiver_borrowed | 12 | ~179 | 0 | -179 |
ori_llvm borrowing_builtin_names | 12 | ~25 | 0 | -25 |
ori_llvm type guards (is_str, is_float) | 12 | ~20 | 0 | -20 |
ori_ir builtin_methods module (123 entries, 945 lines) | 13 | ~945 | 0 | -945 |
consistency.rs allowlists + IR tests | 13 | ~215 | 0 | -215 |
consistency.rs (rewrite with enforcement tests) | 14 | ~295 | ~300 | +5 |
dispatch_coverage.rs allowlists (rewrite with zero-exception tests) | 14 | ~371 | ~50 | -321 |
| Implement 189 missing eval method dispatchers | 14 | 0 | ~1,500-2,000 | +1,500-2,000 |
| Total (excl. new dispatchers) | ~3,005 | ~350 | -2,655 | |
| Total (incl. new dispatchers) | ~3,005 | ~1,850-2,350 | -655 to -1,155 |
Note: This is the combined impact of Sections 09-14. Section 14 itself adds ~300 lines of enforcement tests while the deletions happen across Sections 09-13. The table documents the full plan impact.
14.7 Full Test Suite Execution
Escalating Test Runs
Each step must pass before proceeding to the next. Failures at any level must be investigated and resolved before continuing.
Level 1: Compilation
-
cargo c— all workspace crates compile cleanly -
cargo c -p ori_registry— registry crate compiles -
cargo b— LLVM build compiles (includes ori_registry)
Level 2: Unit Tests (per-crate)
-
cargo t -p ori_registry— registry integrity + purity tests pass -
cargo t -p ori_types— type checker tests pass (no regressions from wiring) -
cargo t -p ori_eval— evaluator tests pass (no regressions from wiring) -
cargo t -p ori_ir— IR tests pass (reduced after migration) -
cargo t -p ori_arc— ARC tests pass (new dependency direction)
Level 3: Integration Tests
-
cargo t -p oric— integration + enforcement tests pass (this is where the new cross-phase enforcement tests live) -
./llvm-test.sh— LLVM unit tests pass (operator strategy dispatch verified)
Level 4: Spec Tests
-
cargo st— all spec tests pass (end-to-end language behavior unchanged) -
cargo st tests/spec/types/— type-specific spec tests pass -
cargo st tests/spec/traits/— trait spec tests pass (includes iterator, derive) -
cargo st tests/spec/methods/— method spec tests pass
Level 5: Full Suite
-
./test-all.sh— everything passes (12,563 tests, 0 failures) -
./clippy-all.sh— no warnings -
./fmt-all.sh— formatting clean
Level 6: Release Verification
-
cargo b --release— release build compiles -
./test-all.shwith release binary — covered by test-all.sh (interpreter + AOT) -
cargo test -p ori_llvm --release— 1689 tests pass (437 lib + 1252 AOT), 0 failures. Fixed pre-existingtest_alias_cycle_terminates#[should_panic]+debug_assert!mismatch with#[cfg(debug_assertions)].
Checklist
- All 6 levels pass in order
- No test was skipped, disabled, or marked
#[ignore](NOTE:test_alias_cycle_terminatesadded#[cfg(debug_assertions)]— this is correct behavior, not a skip. The test relies ondebug_assert!which is absent in release.) - No
#[allow(clippy)]added without justification - No test was modified to pass (tests that fail indicate code bugs, not test bugs)
14.8 Code Journey (Pipeline Integration)
Test the pipeline end-to-end with progressively complex Ori programs that exercise builtin methods across multiple types and phases. This catches issues that unit tests and spec tests miss: silent wrong code generation, phase boundary mismatches, cascading failures across compiler stages, and eval-vs-LLVM behavioral divergence.
Option A: /code-journey skill (preferred)
- Run
/code-journey— 13 journeys completed (arithmetic → iterators), all PASS on both backends - All CRITICAL findings from journey results triaged (fixed or tracked) — zero CRITICAL findings across all 13 journeys
- Eval and AOT paths produce identical results for all passing journeys — all 13 journeys eval=aot
- Journey results archived in
plans/code-journeys/
Option B: Manual pipeline verification (if /code-journey unavailable)
- Write 5+ Ori programs exercising: (superseded by Option A — 13 journeys cover all listed areas)
- Run each with
ori run(eval) andori build && ./binary(AOT) (superseded by Option A) - Verify identical output between eval and AOT paths (superseded by Option A)
- Use
diagnostics/dual-exec-verify.shfor batch comparison (superseded by Option A)
Why this matters: Unit tests verify individual phases in isolation. Code journeys verify that phases compose correctly — data flows through the full pipeline (lexer → parser → type checker → canonicalizer → eval/LLVM) and produces correct results. They use differential testing (eval path as oracle for LLVM path) and progressive complexity escalation to map the exact boundary of what works.
When to run:
- After any change to phase boundaries (new IR nodes, new type variants)
- After changes to monomorphization, ARC pipeline, or codegen
- After adding new language features that affect multiple phases
- As final verification before marking a plan complete
14.9 Exit Criteria (Entire Plan)
These are the exhaustive “done” criteria for the complete Type Strategy Registry plan (Sections 01-14). Every checkbox must be checked before the plan is marked complete.
Structural Guarantees (compile-time)
These guarantees are enforced by Rust’s type system. They hold as long as the code compiles.
- Adding a field to
TypeDefproduces a compile error in every consuming phase (ori_types, ori_eval, ori_arc, ori_llvm) because each phase destructures or readsTypeDeffields. - Adding a
TypeTagvariant produces a compile error in every consuming phase via_enforce_exhaustiveness()dead functions (Roc pattern). Caught atcargo ctime, before any tests run. - Adding a method to a
TypeDefis caught by enforcement tests (not compile errors — method lists are slices). Theevery_registry_method_has_*_handlertests fail for the new method until all phases implement it. -
MethodDeffields are required (no defaults, noOption<T>for essential fields). Omitting a field when constructing aMethodDefis a compile error — including the newpureandbackend_requiredflags. -
ori_registryhas zero dependencies. Thepurity_cargo_toml_has_no_dependenciestest enforces this. Adding any dependency is a test failure. - All
TypeDefconstants areconst-constructible. Thepurity_type_defs_are_consttest enforces this withconst _:declarations. - Core enum types are
Copy. Thepurity_core_enums_are_copytest enforces this. LosingCopyis a compile error in consuming phases.
Behavioral Guarantees (test-time)
These guarantees are enforced by the cross-phase enforcement tests. They hold as long as cargo t -p oric passes.
- Every registry method has a type checker handler.
every_registry_method_has_typeck_handleriterates allBUILTIN_TYPESmethods and verifies ori_types resolves each one. - Every registry method has an evaluator handler.
every_registry_method_has_eval_dispatch_handler(indispatch_coverage.rs) iterates allBUILTIN_TYPESmethods and verifies ori_eval dispatches each one. - Every registry method has an LLVM handler.
backend_required_methods_in_llvm+builtin_coverage_above_threshold(inori_llvm/.../builtins/tests.rs) verify LLVM coverage — strict forbackend_requiredmethods, threshold for the rest (many methods intentionally use runtime calls). - Every non-Unsupported operator strategy has an LLVM handler.
registry_op_strategies_cover_all_operators(already exists inori_llvm/src/codegen/arc_emitter/builtins/tests.rs) verifies all 20OpDefsfields with non-Unsupported strategies are handled. - Every borrowing method is in the ARC borrow set.
every_registry_borrowing_method_in_arc_setverifies thatOwnership::Borrowmethods appear in ori_arc’s borrow inference set. - Every backend-required method is in all backends.
backend_required_methods_in_eval(in oric) +backend_required_methods_in_llvm(in ori_llvm) iterate allBUILTIN_TYPESmethods withbackend_required: trueand verify both backends handle them. - Pure method annotations are consistent.
pure_method_sanityverifies thatpure: truemethods don’t consume their receiver and that a reasonable percentage of methods are marked pure. - No duplicate methods within any type.
no_duplicate_methodscatches copy-paste errors and merge conflicts. - All TypeTag variants have TypeDefs.
all_type_tags_presentcatches new TypeTag variants without corresponding definitions. - TypeTag::all() is synchronized.
type_tag_all_contains_every_variantcatches new TypeTag variants that aren’t inALL_TYPE_TAGS, which would make them invisible to iteration queries. - Methods are sorted.
methods_sorted_by_namemaintains deterministic iteration order. - Operators are consistent.
operator_consistencycatches comparison-without-equality bugs. - Format spec variants are synced.
format_spec_variants_syncedprevents drift between ori_ir enums and phase registrations. - Well-known generics are consistent.
well_known_generic_types_consistentprevents Pool tag unification failures. - ARC COW method lists reference valid registry methods.
consuming_receiver_methods_exist_in_registry+consuming_receiver_only_methods_exist_in_registry+sharing_methods_exist_in_registry(in ori_arc builtins/tests.rs) prevent COW lists from referencing methods that have been renamed or removed from the registry.
Legacy Removal (grep-time)
These guarantees are verified by running the grep commands from Section 14.6.3. All must return 0 results.
- Zero matches for
TYPECK_BUILTIN_METHODSincompiler/(verified clean) - Zero matches for
EVAL_BUILTIN_METHODSincompiler/(verified clean) - Zero matches for
ITERATOR_METHOD_NAMESincompiler/(verified clean) - Zero matches for
DEI_ONLY_METHODSincompiler/(verified clean) - Zero matches for
TYPECK_METHODS_NOT_INincompiler/(verified clean) - Zero matches for
EVAL_METHODS_NOT_INincompiler/(verified clean) - Zero matches for
IR_METHODS_DISPATCHED_VIA_RESOLVERSincompiler/(verified clean) - Zero matches for
COLLECTION_TYPESincompiler/oric/(verified clean) - Zero matches for
METHODS_NOT_YET_IN_EVALincompiler/(verified clean) - Zero matches for
COLLECTION_RESOLVER_METHODSincompiler/(verified clean) - Zero matches for
WELL_KNOWN_GENERIC_TYPESincompiler/(verified clean) - Zero matches for
resolve_str_methodand all 17 sibling resolve functions incompiler/(verified clean) - Zero matches for
receiver_borrowedincompiler/(verified clean) - Zero matches for
borrowing_builtin_namesincompiler/ori_llvm/(verified clean) - Zero matches for
receiver_borrowsincompiler/ori_ir/(verified clean) - Zero matches for legacy
BUILTIN_METHODSincompiler/ori_ir/(verified clean) - Zero matches for
KNOWN_EVAL_ONLYindocs/(verified clean) - All stale comments from 14.6.3a cleaned up (5 WASTE items + 1 DRIFT item — verified in 14.6.3a)
Correctness (runtime)
These guarantees are verified by running the full test suite.
-
./test-all.shpasses with zero failures (12,564 passed, 0 failed) -
./llvm-test.shpasses with zero failures (included in test-all.sh: 438 ori_llvm + 1252 AOT + 243 LLVM spec) -
cargo stpasses with zero failures (included in test-all.sh: 4169 passed) -
./clippy-all.shpasses with zero warnings -
./fmt-all.shpasses (no formatting changes needed) -
cargo b --release && ./test-all.shpasses (release build regression check — 12,564 passed, 0 failed) - No existing test was deleted, modified, or marked
#[ignore]to achieve a passing suite - No
#[allow(clippy)]was added without areason = "..."justification - Code journey passes — dual-exec-verify on types/, collections/, traits/ — zero mismatches between eval and AOT
Documentation
These guarantees verify that the plan’s output is documented and discoverable.
-
ori_registry/src/lib.rshas a crate-level//!doc comment explaining the mission, purity contract, and usage pattern (verified accurate) - Every
pubitem inori_registryhas a///doc comment (spot-checked ~95% coverage) -
.claude/rules/updated with registry patterns (.claude/rules/registry.md— adding types, methods, sync points, test commands) -
plans/builtin_ownership_ssot/marked as SUPERSEDED by type_strategy_registry (directory removed; supersedes note in index.md) -
plans/roadmap/sections updated — supersession note added to00-overview.md; historical[x]items preserved (describe work at time of completion) -
docs/compiler/design/files verified for accuracy (5 files updated;08-evaluator/index.md:228no longer referencesKNOWN_EVAL_ONLY— correctly referencesori_registry) - This section (14) documents the complete elimination checklist
- The index.md in
plans/type_strategy_registry/is updated with final status (status: resolved, Section 14 marked Complete)
Known Blockers
-
Channel
Valuerepresentation: The evaluator has noValue::Channelvariant. The 9 Channel methods inMETHODS_NOT_YET_IN_EVALcannot be implemented without either (a) addingValue::Channel, (b) excluding Channel from eval enforcement, or (c) settingbackend_required: falseon Channel methods. This decision must be made before Section 14 can close. -
dispatch_builtin_method_strAPI limitation: The test function only exercisesBuiltinMethodResolver(priority 2), missingCollectionMethodResolver(priority 1). The 20 methods inCOLLECTION_RESOLVER_METHODSare correctly implemented but invisible to the current test API. Fix requires either a full-chain test API or a separateCollectionMethodResolvertest.
Plan Completion Summary
When all exit criteria above are satisfied:
ori_registryis the single source of truth for all builtin type behavioral specifications- Every compiler phase (ori_types, ori_eval, ori_arc, ori_llvm) reads from ori_registry
- Cross-phase drift is structurally impossible (compile-time) or immediately detected (test-time)
- Zero allowlists remain (including
METHODS_NOT_YET_IN_EVAL,COLLECTION_RESOLVER_METHODS, andWELL_KNOWN_GENERIC_TYPES) - Zero legacy parallel lists remain (ARC COW lists are intentionally kept as documented in the parallel lists section above)
- ~2,600+ lines of manual sync infrastructure have been eliminated
- Adding a new builtin method requires exactly one change: a
MethodDefentry inori_registry. All enforcement tests then guide the implementer to add handlers in each phase. - ARC COW method lists are cross-validated against the registry to prevent drift
The Type Strategy Registry plan is complete.