Files
AILang/crates/ailang-core
Brummel ee4107c721 fix(check): honour the written element type-arg on polymorphic (new T …)
A `(new RawBuf (con Int) 3)` whose buffer is never observed by a later
get/set passed `ail check` but crashed `ail build` with
`RawBuf_new__Unit has no registered intercept`, and a forbidden
`(new RawBuf (con Unit) 3)` was wrongly accepted at check. Both legs
share one root: the `Term::New` desugar discarded the author's written
`NewArg::Type` before anything could use it.

This was never an inference problem. The element type is EXPLICITLY
known — the author wrote `(con Int)`. The defect was that the desugar
threw that known type away and relied on re-discovering it from
downstream use; with no use (size-only), `RawBuf.new`'s result-only
forall var stayed unpinned, mono Unit-defaulted it, and codegen aborted
on the unregistered `RawBuf_new__Unit` intercept. The fix USES the
written type instead of inferring it.

Mechanism:
- desugar.rs: a polymorphic `(new T <elem> …)` (carrying a written
  `NewArg::Type`) now SURVIVES into check instead of being lowered to
  `(app T.new …)`. A monomorphic `(new Counter 42)` (no type-arg) keeps
  the raw-buf.4 app-lowering unchanged.
- lib.rs synth `Term::New` arm: (a) validates the written element type
  against the constructed type's `param_in` set via
  `check_type_well_formed`, firing `ParamNotInRestrictedSet` naming the
  forbidden element (leg 2) — synth is the correct site because it has
  Env/registry access, unlike `pre_desugar_validation`; (b) binds the
  result-only forall var DIRECTLY to the written type (`?a := Int`, a
  trivial binding, no inference channel) by pushing a `FreeFnCall` whose
  metas are the written concrete types, so mono mints
  `RawBuf_new__<elem>` and never `__Unit`.
- mono.rs: `rewrite_mono_calls` and `interleave_slots` gain matching
  `Term::New` arms that each consume exactly one free-fn slot at a
  type-arg-bearing `Term::New` (mirroring synth's single observation,
  pushed before the value-args), and `rewrite_mono_calls` reduces the
  node to `(app <mangled> <value-args>)` so codegen never sees
  `Term::New` and the lib.rs:2200 `unreachable!` stays valid. The two
  walkers stay in lockstep.

The Unit-default policy in mono stays correct for genuinely-unobservable
vars (`is_empty(Nil)` etc.); only the case with a written `NewArg::Type`
is changed.

Alternatives rejected: an additive `type_args` field on `Term::App`
(~100 construction sites across 7 crates — a detour that builds a new
transport slot only to copy the already-known type into it); a runtime
type-witness parameter on `RawBuf.new` (invents a runtime value for a
pure type property); an identity-lambda wrapper carrying the element in
its param-type (hides a semantic property in a runtime construct). The
written type belongs where the author put it, carried to the one point
that consumes it.

Verification: both RED legs (committed 61b9d3f) now green; full
check/codegen/core/surface suites green; intercepts bijection and
hash-pin tests green; existing raw-buf int/float/bool fixtures still
build and run leak-clean (live=0).

Out of scope (pre-existing at HEAD, separate issue): a monomorphic
`(new Counter 42)` over a user ADT fails with `unknown variable:
Counter.new` — unrelated to the type-arg drop fixed here.

closes #51
2026-05-31 03:03:14 +02:00
..