iter 24.tidy: close 5 actionable drift items from audit-24
Documents the three iter-24.3 strengthenings as load-bearing invariants and tightens two error-handling sites: T1: DESIGN.md gains new subsection §Cross-module references in synthesised bodies (between Resolution-and-monomorphisation and Defaults-and-superclasses) documenting three invariants installed in iter 24.3 — (1) MonoTarget::FreeFn::type_args carries canonical types post-collection via normalize_type_for_lookup; (2) post-mono synthesised body cross-module refs may bypass the source template's import_map (codegen falls back to module_user_fns / module_def_ail_types); (3) FreeFnCall synth pushes one ResidualConstraint per declared forall-constraint with rigid vars substituted by fresh metavars. T2: codegen_import_map_fallback_pin.rs (integration test) asserts the synthesised prelude.print__<IntBox> body references show_user_adt.show__<IntBox> AND prelude module's imports do not contain show_user_adt — proving the cross-module ref bypasses import_map at codegen. T3: polyfn_dot_qualified_branch_pin.rs (integration test) asserts bare-name print f (f : Int -> Int) fires exactly one no-instance diagnostic at typecheck with zero unknown-variable diagnostics — proving the bare-name resolution reaches the dot-qualified synth branch where the constraint-residual push fires. T4: check/lib.rs:2858 unwrap_or_default() replaced with .expect() carrying the registry-coherence message — class_methods index drift now surfaces explicitly rather than rendering NoInstance with an empty method name. T5: mono.rs gains apply_subst_and_normalize helper (Option<Type> return) extracted from two byte-identical call sites at collect_mono_targets and collect_residuals_ordered. Each call site retains its own rigid-var / unit-default policy in the None arm (site 1: rigid → has_rigid+break, unbound → Type::unit; site 2: non-concrete → Type::unit). Byte-identity invariant on mono-symbol hashes enforced by construction. Tests: 558 passed (was 556 + 2 new pins). No production semantic change — pure documentation + test pin + error-handling tightening + helper refactor. bench/cross_lang exit 0; bench/compile_check + bench/check exit 0 this run (latency.implicit_at_rc / latency.explicit_at_rc / bench_list_sum.bump_s noise envelope unobserved, lineage continues at 10th consecutive observation without firing this run).
This commit is contained in:
@@ -1692,6 +1692,77 @@ unresolved at the entry point — is a static error, not a runtime
|
||||
one. This is the LLVM-friendly form and is consistent with
|
||||
Decision 10's performance commitment.
|
||||
|
||||
### Cross-module references in synthesised bodies
|
||||
|
||||
The unified mono pass (per Decision 11's milestone-23.4 reorganisation)
|
||||
synthesises mono symbols for polymorphic free fns and class-method
|
||||
instances in the symbol's owner module — e.g. `print__Int` lives in
|
||||
`prelude` (because `print` is defined in the prelude), but a
|
||||
user-ADT call site `print (MkIntBox 7)` causes synthesis of
|
||||
`prelude.print__IntBox` whose body references
|
||||
`show_user_adt.show__IntBox` (the user instance lives in the
|
||||
user-defining module per Decision 11 coherence). The synthesised body
|
||||
crosses a module boundary the source template did not.
|
||||
|
||||
Three invariants make this work, all installed in milestone 24's iter
|
||||
24.3 and worth keeping load-bearing:
|
||||
|
||||
1. **`MonoTarget::FreeFn::type_args` carries canonical types
|
||||
post-collection.** At every site where `subst.apply(m)` produces a
|
||||
concrete substitution that enters `MonoTarget::FreeFn::type_args`
|
||||
(`crates/ailang-check/src/mono.rs::collect_mono_targets` and
|
||||
`::collect_residuals_ordered`), the resolved `Type` must be passed
|
||||
through `Registry::normalize_type_for_lookup(caller_module, &t)`
|
||||
before being pushed. The downstream synthesised body's Phase 3
|
||||
rewrite cursor (which runs in the OWNER module's context, not the
|
||||
caller's) keys lookups in `Registry::entries` by the canonical
|
||||
qualified form; a bare type-con reference at the type_args layer
|
||||
silently drops the cross-module mono-symbol synthesis and leaves a
|
||||
bare `Var "show"` post-mono that codegen later rejects with
|
||||
`unknown variable`.
|
||||
|
||||
2. **Post-mono synthesised body cross-module references may bypass
|
||||
the source template's `import_map`.** `prelude` does not import
|
||||
user modules (the auto-injection runs one-way: user workspaces
|
||||
import prelude, never the inverse). But a synthesised body for
|
||||
`prelude.print__<UserType>` references `<user_module>.show__<UserType>`,
|
||||
created by mono — not by the prelude source. Codegen's
|
||||
cross-module name-resolution must accept this: at
|
||||
`crates/ailang-codegen/src/lib.rs::resolve_top_level_fn` (Var-
|
||||
resolution), `::lower_app`'s cross-module call arm, and
|
||||
`::synth_with_extras`'s Var arm, the resolution first tries the
|
||||
current module's `import_map`, then falls back to a direct
|
||||
`module_user_fns` / `module_def_ail_types` lookup against the
|
||||
prefix. Both ends were independently typechecked under their own
|
||||
module contexts before mono ran; the cross-module reference is a
|
||||
post-mono construct, not a source-language one.
|
||||
|
||||
3. **FreeFnCall synth pushes one residual per declared forall-
|
||||
constraint.** At `crates/ailang-check/src/lib.rs`'s synth Var arm
|
||||
for the `prefix.suffix` free-fn path, when the type is a
|
||||
`Type::Forall { vars, constraints, body }`, instantiation produces
|
||||
fresh metavars for the `vars` AND pushes one `ResidualConstraint`
|
||||
per entry in `constraints` (with rigid vars substituted by the
|
||||
freshly-generated metavars). The downstream discharge loop at
|
||||
`check_fn`'s post-synth phase resolves each residual against the
|
||||
workspace registry; if no instance satisfies the residual at the
|
||||
unified concrete type, the `NoInstance` diagnostic fires at
|
||||
typecheck (correctly), not at codegen (confusingly). Without the
|
||||
residual push, milestone-23-shape negative cases (e.g. `print f`
|
||||
where `f : Int -> Int`) silently typecheck and surface as
|
||||
`unknown variable: show` from codegen instead of the right
|
||||
typecheck-phase NoInstance Show.
|
||||
|
||||
The three invariants are lockstep partners: invariant (1) creates the
|
||||
cross-module reference, invariant (2) makes codegen able to resolve
|
||||
it, invariant (3) makes the typecheck-time discharge fire correctly
|
||||
when no instance exists. A future refactor that loosens any one of
|
||||
the three breaks the user-ADT trajectory; the test pins at
|
||||
`crates/ail/tests/codegen_import_map_fallback_pin.rs` (invariant 2),
|
||||
`crates/ail/tests/polyfn_dot_qualified_branch_pin.rs` (invariant 3
|
||||
+ lockstep), and the existing `crates/ail/tests/show_user_adt`
|
||||
fixture (full trajectory) collectively protect the contract.
|
||||
|
||||
### Defaults and superclasses
|
||||
|
||||
**Defaults.** A `ClassDef.methods[i].default` is either `null` (the
|
||||
|
||||
Reference in New Issue
Block a user