Files
AILang/docs/journals/2026-05-13-iter-mq.tidy.md
T
Brummel 1b6cbcb68b iter mq.tidy: close 4 actionable drift items from audit-mq
T1 [high-1]: refine_multi_candidate_residual rigid-var filter now
requires class + type-unification via constraint_type_matches, so
forall a b. prelude.Show a, userlib.Show b => ... shapes discriminate
by which typevar the residual is on. Discharge-time only; synth-time
twin doesn't have a residual type to unify against (fresh metavar
constructed after dispatch).

T2 [high-2]: qualifier_is_class_shape extracted as pub(crate) helper
adjacent to parse_method_qualifier; broadened to accept PascalCase
single-segment qualifiers like Show.show alongside module-qualified
ones. Same-module bare-class call sites now reachable; symmetric to
mq.1 canonical-form rule.

T3 [medium-1]: any_candidate_class_has_instance pub(crate) helper
gates the class-method-shadowed-by-fn warning closure on registry
instance presence (any candidate class, any type). Conservative
tightening of the spec rule per Boss Q2 decision.

T4 [medium-2]: four DESIGN.md Data Model schema fragments
(SuperclassRef, InstanceDef.class, Type::Con.name, Constraint.class)
annotated with canonical-form cross-references.

Two trip-wires fixed inline: parse_method_qualifier docstring
coherence, mq3 in-crate test fixture-repair (registry instance
injection — analogous to mq.3 typeclass_22b3 pattern).

5/5 tasks. 548 tests green (was 545 + 3 new mq_tidy_* unit tests).
bench/compile_check.py + cross_lang.py exit 0; check.py exit 0
both runs with noise-class metric migration on
latency.implicit_at_rc.* max-tail (6th-consecutive observation
since audit-cma). Baseline pristine.
2026-05-13 02:49:26 +02:00

12 KiB

iter mq.tidy — Close 4 actionable drift items from audit-mq

Date: 2026-05-13 Started from: 64d3feeb97 Status: DONE Tasks completed: 5 of 5

Summary

mq.tidy closes the four actionable drift items audit-mq surfaced (commit f382931), moving the module-qualified-class-names milestone from "structurally complete" to "drift-clean ready for next milestone". T1 extends the discharge-time refine_multi_candidate_residual rigid-var filter from class-only to class + type-unification via the existing constraint_type_matches helper, so two same-class declared constraints on different typevars are correctly discriminated by the residual's typevar identity (high-1). T2 extracts the inline qualifier_is_class_shape predicate as a free pub(crate) helper and broadens it to accept PascalCase single-segment qualifiers ("Show.show") alongside module-qualified ones ("prelude.Show.show") — same-module bare-class call sites are now reachable, symmetric to the canonical-form rule at the schema level (high-2). T3 introduces the any_candidate_class_has_instance predicate and gates the class-method-shadowed-by-fn warning closure on it, so class methods with zero registry instances anywhere no longer fire the warning; conservative approximation of the full spec rule per Boss Q2 (the arg type required for the strict version is unavailable at the Var-arm) (medium-1). T4 annotates the four Data Model schema fragments (SuperclassRef, InstanceDef.class, Type::Con.name, Constraint.class) with the canonical-form cross-reference, so a schema-section reader without the prose context no longer sees bare-only (medium-2).

T5 verification: 548 tests green (was 545 pre-iter + 3 new mq_tidy_* unit tests). bench/compile_check.py exit 0 (24/24 stable after a one-run noise blip absorbed under tolerance on re-run). bench/cross_lang.py exit 0 (25/25 stable). bench/check.py exit 0 both runs; metric-identity-migrating noise on the latency.implicit_at_rc.* max-tail cluster — 5th-consecutive audit observation per audit-mq's lineage, baseline pristine. The mq3_class_method_shadowed_by_fn_warning_fires in-crate test was a trip-wire: pre-tidy it used Registry::default() (no instances) and the warning fired anyway because the old gate didn't check instances; post-tidy it correctly suppresses, so the fixture was updated to ship a clsmod.Show Int registry instance — fixture-repair as a coherence consequence of the deliberately tightened spec rule. mq3 E2E (3 tests)

  • typeclass_22b3 (18 tests) all stay green; the E2E fixtures already ship instances for the shadowed class.

Per-task notes

  • mq.tidy.1 — Rigid-var refinement type-unification leg. refine_multi_candidate_residual filter at lib.rs:2163-2180 extends from dc.class == c to dc.class == c && constraint_type_matches(&dc.type_, &residual.type_). New unit test mq_tidy_rigid_var_filter_uses_type_unification pins the same-class-different-typevar discrimination (prelude.Show a + userlib.Show b declared, residual on Var aResolved("prelude.Show"), userlib.Show drops because its declared type Var b doesn't unify with the residual's Var a). Architecture decision per plan: only the discharge-time call site needs the type-unification leg; the synth-time resolve_method_dispatch is invoked with concrete_arg_type: None and constructs the residual's metavar AFTER the dispatch call, so the rigid-var leg there has no residual type to unify against (fresh metavar would trivially unify with any declared-constraint type, degenerating to class-only filter). The class-only filter at synth time is therefore semantically correct, not drift.

  • mq.tidy.2 — Bare-class qualifier shape accepted. Inline qualifier_is_class_shape predicate at the synth Var-arm dispatch entry (originally lib.rs:2570-2575) extracted as a free pub(crate) fn qualifier_is_class_shape(&Option<String>) -> bool helper adjacent to parse_method_qualifier. Predicate body: None → true (bare method); Some(q) with inner dot → true (<module>.<Class>); Some(q) single-segment → first char uppercase (PascalCase). Bare-fn qualifiers ("std_list", lowercase) correctly reject and fall through to the cross-module-fn arm. Call site at the original location reduced to a one-liner. New unit test mq_tidy_bare_class_qualifier_shape_accepted covers all four shapes.

  • mq.tidy.3class-method-shadowed-by-fn warning tightened. New pub(crate) fn any_candidate_class_has_instance(...) helper using BTreeMap range-scan (O(log n) per candidate, O(|candidates|

    • log n) total) checks the workspace registry for at least one instance under any candidate class. Warning closure at lib.rs:2479-2502 (now ~2498-2540 post-T2-insertion-shift) builds a registry_unit_view: BTreeMap<(String, String), ()> per closure invocation and calls the helper before emitting. New unit test mq_tidy_shadow_warning_suppressed_when_no_instance pins both directions (empty registry → suppress; one instance → fire). The existing mq3_class_method_shadowed_by_fn_warning_fires in-crate test was a trip-wire — its fixture used Registry::default() (no instances) and pre-tidy the warning fired anyway because the old gate didn't check instances. Post-tidy the spec rule correctly suppresses; fixture repaired by injecting a clsmod.Show Int registry entry directly into ws.registry.entries so the test continues to assert positive-fire behaviour under the post-tidy rule. The three mq3 E2E fixtures (mq3_class_eq_vs_fn_eq
    • mq3_two_show_*) already ship instances on the shadowed class, so they stayed green without modification.
  • mq.tidy.4 — DESIGN.md schema fragments. Four trailing-comment annotations: SuperclassRef ("superclass" field at line 2139), InstanceDef.class (line 2152), Type::Con.name (line 2253), and Constraint.class (line 2273). Class-ref annotations cross-reference §"Class names" / mq.1; the Type::Con annotation cross-references §"Type::Con name scoping" / ct.1 (the actual section title — the plan's "§Type names" / "Canonical type names" search did not match exactly; used the canonical anchor title verified via grep). Doc count of "canonical form" went from 1 → 5 (+4 as planned). All annotations are inline trailing-comment appends; no line-count change in DESIGN.md (2678 → 2678).

  • mq.tidy.5 — Integration verification. cargo test --workspace --no-fail-fast → 548 passed, 0 failed (was 545 pre-iter + 3 new mq_tidy_* unit tests). bench/compile_check.py exit 0; first run showed 1 regression (build_O0_ms.bench_list_sum_explicit +5.43% within 20.0% tolerance, classified ok; the summary's "1 regressed" count is a false-positive on the first run, second run shows 0 regressed, 24/24 stable). bench/cross_lang.py exit 0 both runs, 25/25 stable. bench/check.py exit 0 both runs; run 1 had 3 regressed / 3 improved (all latency.implicit_at_rc.* max-tail metrics — same noise class the audit-mq journal documented across 4 consecutive runs); run 2 had 0 regressed / 1 improved / 62 stable — exactly the metric-identity-migration pattern the audit named as variance, not signal. Baseline pristine per the conservative-call convention (6th-consecutive observation now). Plan Step 5 (prelude roundtrip via cargo run --bin ail -- check examples/prelude.ail.json) returned exit 1 with "module name 'prelude' is reserved" — but this is identical to pre-edit behaviour (verified via git stash), not a regression from this iter. The plan's expectation was inaccurate; the actual sanity gate (DESIGN.md doc edits do not break prelude consistency) is satisfied by the workspace test suite passing 548/548.

Concerns

  • Plan Task 3 Step 6 anticipation was off for one in-crate test. The plan said: "Expected: PASS — the existing positive fixtures (mq3_class_eq_vs_fn_eq, typeclass_22b3::rewrite_walker_skips_locally_shadowed_class_method) all ship registry instances for the shadowed class, so the tightened predicate continues to admit the warning emission." This was correct for those two fixtures (both E2E), but the in-crate unit test mq3_class_method_shadowed_by_fn_warning_fires builds its workspace with Registry::default() (zero instances) and pre-tidy relied on the looser rule. Post-tidy it correctly suppresses; the fixture was repaired inline by injecting a clsmod.Show Int registry entry. This is exactly analogous to the typeclass_22b3::rewrite_walker_skips_locally_shadowed_class_method trip-wire mq.3 documented — same pattern, different test. Fixture-repair as a coherence consequence of a deliberately tightened spec rule; the test's intent ("warning fires when a free fn shadows a class method") is unchanged.

  • Plan Task 5 Step 5 prelude check expectation was off. The plan said cargo run --bin ail -- check examples/prelude.ail.json expected ok exit 0; the actual behaviour is exit 1 with the "module name 'prelude' is reserved (auto-injected by the loader)" error. This is identical to pre-edit behaviour (verified via git stash); the prelude file is the loader's auto-injected module, not a workspace entry, so ail check against it has always failed by design. The right sanity gate for "DESIGN.md doc edits do not break prelude consistency" is the workspace test pass (548/548), which exercises every workspace test that uses the prelude. The plan's prescribed command can be removed from future tidy plans.

  • T2 docstring update on parse_method_qualifier was not explicitly in the plan. The plan asked for adding the new qualifier_is_class_shape helper but did not enumerate the paragraph update on parse_method_qualifier's docstring. The old docstring said "Callers gate on the qualifier shape: a class qualifier is always qualified per mq.1's canonical-form rule" — which the broadened gate directly contradicts (bare PascalCase qualifiers now go to class-dispatch). Updating the existing helper's documentation to reference the new helper and remove the now-incorrect claim is strictly required to keep the helper's documentation coherent with the requested behaviour change; it is the docstring analog of "code edits strictly required to make the requested changes compile." Defensible as a coherence consequence, not unrequested scope creep.

Known debt

  • bench/check.py max-tail noise envelope. 6th-consecutive observation of the latency.implicit_at_rc.* max-tail metric identity migrating between runs. The conservative-call convention has held the baseline pristine across 5 prior audits; this iter adds run 6 to the lineage. A future audit may consider ratification via --update-baseline if the pattern persists for another 2-3 audits; until then, the lineage is the documentation.

  • mq.tidy did not deliver the [low-1] (Trajectory B + D E2E) or [low-2] (mono env shape) roadmap-backlog items. Same scope as audit-mq's deferral; these wait for the multi-class workspace shape to land downstream.

  • mq.tidy did not refactor synth(...)'s 10-mut-ref signature. audit-mq's [medium-3] item was explicitly acknowledged-debt, not in tidy scope. Same disposition; future iter may revisit if a natural refactor opportunity arises.

Files touched

Code:

  • crates/ailang-check/src/lib.rs

Docs:

  • docs/DESIGN.md
  • docs/journals/2026-05-13-iter-mq.tidy.md (this file, new)

No new test files; the three new mq_tidy_* unit tests are inline in crates/ailang-check/src/lib.rs's mod tests block.

Stats

bench/orchestrator-stats/2026-05-13-iter-mq.tidy.json