d81ea93de6938ceed62cd6278be60e25b8cb56d4
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
600565d356 |
fix(check): localize own-ADT type-args in free-fn mono; complete the lower_to_mir producer
Additive producer-side work for the typed-MIR milestone (spec
docs/specs/0060-typed-mir.md), surfaced while building the mir.1b
codegen-consumption switch. Nothing here consumes MIR — codegen is
untouched; the whole existing suite stays green and one new RED pin
goes green. The mir.1b codegen switch itself stays in the working tree.
Headline fix — own-ADT type-arg localization in free-fn mono:
monomorphise_workspace's free-fn arm normalises every observed
type-arg through Registry::normalize_type_for_lookup. That
normalisation is load-bearing for cross-module dedup and a stable
specialisation symbol name, but it qualifies a *defining-module-own*
ADT to `<module>.<T>`. synthesise_mono_fn_for_free_fn then substitutes
those qualified type-args into the polymorphic source signature and
body, which are in the module's own *bare* convention (the qualify
pre-pass deliberately leaves own-module type-cons bare — lib.rs:4358,
"the current module sees its own types bare"). The result is a
self-inconsistent specialisation: e.g. fold_left specialised with
accumulator b := List<Int> minted
((std_list.List<Int>, Int) -> std_list.List<Int>,
std_list.List<Int>, List<Int>) -> std_list.List<Int>
— a qualified accumulator parameter against a bare list argument and a
bare Lam body. check_workspace never hits this (it synths the pre-mono
bodies, bare throughout); the old codegen tolerated it via the
now-deleted synth_arg_type's loose type-tracking. The post-mono synth
re-entry in lower_to_mir does full unification and correctly rejects
it: `expected std_list.List<Int>, got List<Int>`.
Fix: localize_own_types (mono.rs) strips the `<defining_module>.`
prefix back to bare for the module's own ADTs, applied to the type-args
before they are substituted, so the synthesised signature and body stay
uniformly bare-own. The *stored* type_args remain normalised, so the
symbol name and the Phase-3 rewrite-cursor key on the same canonical
form — the byte-identity invariant between synthesis and rewrite is
untouched. The ClassMethod arm needs no analogue: it already
substitutes the un-normalised target.type_ (mono.rs:841).
Alternatives rejected: (a) drop the normalisation in the shared
apply_subst_and_normalize helper — breaks cross-module dedup, mints
duplicate specialisations under divergent names. (b) Make
lower_to_mir's synth treat `<owner>.T` and bare `T` as equal during
unification — papers over the producer inconsistency in the consumer
and contradicts the established own-types-stay-bare invariant the rest
of check relies on. (c) Qualify the whole synthesised def own->qualified
— fights lib.rs:4358 and would require the lower path to also flip,
cascading the convention change through synth.
Producer completeness (additive, mirrors existing guards):
- lower_module skips Type::Forall defs (lower_to_mir.rs), mirroring
emit_module's guard. Polymorphic defs are stale source kept for
round-trip; their bodies carry mono-rewritten call names for
specialisations that were never synthesised, so a synth re-entry
would hit UnknownIdentifier.
- lower_module lowers non-literal const bodies to typed MTerm, carried
in MirModule.consts (+ MirConst in ailang-mir). A non-literal const
(e.g. a List ctor expression) needs a typed body once the consumer
walk takes &MTerm; literal consts emit a global and need no body.
- MirModule gains `ast: Module`, populated by lower_module — forward
scaffolding the mir.1b codegen consumption reads for module
structure. Populated here, consumed by the in-tree switch.
Verification: full `cargo test --workspace` green (697 passed, 0 failed)
with the codegen-consumption switch stashed out, including the new pin
crates/ailang-check/tests/lower_to_mir_ty.rs::poly_free_fn_accumulating_into_own_adt_elaborates
(RED before the localize fix with the exact mismatch above, GREEN after)
and the full e2e corpus (the own-ADT-poly demos elaborate clean and run
through the old codegen). This proves the producer fix is
regression-free and the remaining 22 e2e reds in the working tree are
codegen-consumption defects, not producer defects.
|
||
|
|
135f4ceed7 |
feat(check): mir.1a — typed-MIR types + post-mono lower_to_mir + elaborate_workspace (additive)
First half of spec iteration mir.1 (docs/specs/0060-typed-mir.md, plan docs/plans/0114-mir.1a-mir-infrastructure.md). Purely additive: a new leaf crate plus a post-mono lowering walk plus the front-end entry. Nothing consumes MIR yet — codegen and the CLI are untouched — so the whole existing suite stays green (102 test-result-ok lines, 0 failed; the one ignored is the #49 pin, which lifts at mir.4). mir.1b flips codegen to consume MIR and deletes the synth_with_extras mirror. What lands: - `ailang-mir` (new leaf crate, depends only on ailang-core): MTerm / MArg / MArm / MLoopBinder / MNewArg / Callee / Mode / StrRep / MirDef / MirModule / MirWorkspace, structurally complete over all 17 Term variants plus the dedicated Str split (MTerm::Str{lit,rep}). Every later-iteration annotation field exists now at a mir.1 default (App.callee = Indirect, MArg.mode/consume_count = Owned/1, Str.rep = Static, New.elem = None); the struct shape will not change across mir.2–mir.5, only the values. MTerm::ty() reads the carried type. - `ailang-check::lower_to_mir`: the post-mono walk. Carries no second type engine — it calls the canonical `synth` per node with fresh throwaway inference scaffolding (subst/counter/residuals/…), applies the substitution, and threads only the lexical locals/loop_stack, maintained by the same push/pop rules synth uses (Let, Loop, Lam, LetRec, and Match via synth's own type_check_pattern). This is the "one engine, not two" property the milestone buys, on the producer side. - `ailang-check::elaborate_workspace`: the single front-end entry — check (diagnostics gate) → desugar+lift → monomorphise → lower_to_mir — transcribed from the CLI build path. check_workspace stays for the diagnostics-only callers. - Three ty-fill pins (crates/ailang-check/tests/lower_to_mir_ty.rs) load the #51/#53/#49 witnesses as fixtures and elaborate the whole workspace, so lower_to_mir is exercised over the full prelude+kernel, not just the tiny witness. Two new witness fixtures (examples/new_{rawbuf_size_only,counter_user_adt}.ail); #49 reuses the existing loop_recur_str_binder_no_leak_pin.ail. Deviations from the plan, all forced by ground truth and verified against the live code before commit: - FnDef.export is Option<String>, not bool — dropped from MirDef (the plan's note allowed this fallback). Codegen reads FnDef.export directly, unaffected. - fn_param_types did not exist — lifted the param-type extraction into a shared pub(crate) fn and rewired both mono.rs sites (collect_mono_targets, collect_rewrite_targets) to it, so the param-type source cannot drift between mono and lower_to_mir. The helper unwraps a top-level Forall then reads Type::Fn.params — identical to the inner_ty extraction it replaces; the early-return non-fn guard is preserved at both sites. Behaviour-preserving (whole suite green; this is the only edit to existing mono behaviour). - lower_module mirrors two more mono.rs scaffolding rules the plan under-transcribed: skip intrinsic-bodied fns (they are signature-only; lowering them would hit synth's Term::Intrinsic guard) and seed env.current_module + env.globals + env.imports per module so synth's Var lookup resolves post-mono specialisations (e.g. compare__Int) in the prelude. - The #53 pin asserts the lowered (new Counter 42) init carries ty Counter, not that it is an MTerm::New: a monomorphic `new` over a user ADT is desugared to a dotted call `(app Counter.new 42)` before lower_to_mir runs (desugar.rs:1078-1098), so it lowers to MTerm::App. MTerm::New survives only for a polymorphic `new` carrying a written NewArg::Type (the #51 RawBuf path, covered by the rawbuf pin). The pin's intent — ty-fill correctness — is preserved; the structural variant is mir.2/mir.5 territory. - ailang-mir uses workspace-inherited Cargo fields for consistency with the existing crates (added it to [workspace.dependencies]). refs #49 |