All 176 files in the four accumulating directories now use a zero-padded 4-digit counter prefix that reflects creation order (`NNNN-slug.md`). The counter is assigned per directory in strict git-log creation order; ties broken alphabetically by original name. The old `YYYY-MM-DD-` prefix on docs/specs/ and docs/plans/ files is dropped — the date is recoverable from git log and the counter carries the ordering. A file's counter is stable for the life of the file: never reassigned, never reused, never compacted. Deleted files retire their counter; subsequent files do not fill the gap. This is the property that lets cross-references stay literal — refs use the full filename including the counter (`design/contracts/0007-honesty-rule.md`) so they grep cleanly and resolve directly without a glob step. 313 cross-references updated across .md/.rs/.toml/.c/.json files (test pins, include_str! paths, design-INDEX entries, baseline notes, runtime C comments, inter-contract markdown links incl. bare basename and `../models/foo.md` forms). CLAUDE.md gets a new "File-naming convention" section spelling out the rule and rationale. skills/brainstorm/SKILL.md and skills/planner/SKILL.md updated so new spec/plan creation produces counter-prefixed names from the start. The full test suite (cargo test --workspace) passes.
2.5 KiB
Method dispatch
Method dispatch
The class-method resolution rule that pairs with the
typeclasses contract; the Show/Eq/Ord instances
the rule dispatches against live in prelude classes.
Dispatch is two-mode:
Polymorphic call sites (inside a fn body with forall +
constraint set): the constraint names the class via the qualified
Constraint.class field (canonical form: bare for same-module,
<module>.<Class> for cross-module; see
memory model for the canonical-form rule).
Synth's residual carries the class name directly;
constraint-discharge at fn-body-end matches against the workspace
registry by (class, type_hash) key.
Monomorphic call sites: synth consults the workspace-flat
Env.method_to_candidate_classes: BTreeMap<MethodName, BTreeSet<QualifiedClassName>> index, then runs the 5-step
dispatch rule:
- Parse the
Term::Var.namefor an optional class qualifier (last-dot-segment is the method name; everything before is the qualified class). - If
method_to_candidate_classeshas no entry for the method name, fall through to the existing Var-arm branches (free fn lookup, dot-qualified cross-module). - Qualifier present: filter candidates to the named class. Empty
result fires
UnknownClass. Singleton survivor proceeds. - Qualifier empty (bare-method form): singleton candidate proceeds directly; multiple candidates yield a multi-candidate residual for discharge-time refinement.
- At discharge, refinement runs: concrete
type_filters candidates via the workspace registry; rigid-vartype_filters via the active fn's declared constraints (env.active_declared_constraints). Single survivor discharges; multiple survivors fireAmbiguousMethodResolution(concrete) orMissingConstraint(rigid-var); zero survivors fireNoInstance(concrete) orMissingConstraint(rigid-var).
The method_to_candidate_classes index is the load-bearing data
structure for this routing — its construction in build_check_env
inverts the per-module class_methods maps (themselves tuple-keyed
by (qualified-class, method)) to a workspace-flat
method-name-to-class-set map.
Class-fn collisions resolve at the call site, not at workspace load
time: the fn lookup precedence (locals → caller-module-fn →
imported-fn) runs ahead of the class-method branch. When both
sides have a match, the fn wins and a class-method-shadowed-by-fn
warning surfaces the shadow.
Ratified by: crates/ail/tests/show_no_instance_e2e.rs.