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
This commit is contained in:
@@ -1051,29 +1051,50 @@ impl Desugarer {
|
||||
in_full
|
||||
}
|
||||
Term::New { type_name, args } => {
|
||||
// raw-buf.4: (new T <types…> <values…>) desugars to
|
||||
// (app T.new <values…>) — a type-scoped call so synth's
|
||||
// dotted-name branch resolves it through the
|
||||
// TypeDef-first ladder and the raw-buf.3 scope threading
|
||||
// mints `T_new__<elem>`. The leading NewArg::Type is
|
||||
// dropped: the AST carries no type-args on App, and the
|
||||
// element type is recovered by ordinary inference from
|
||||
// how the result is used (spec § Term::New desugar).
|
||||
// Runs before check, so no Term::New reaches the
|
||||
// type-checker or codegen.
|
||||
let value_args: Vec<Term> = args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
NewArg::Value(v) => Some(self.desugar_term(v, scope)),
|
||||
NewArg::Type(_) => None,
|
||||
})
|
||||
.collect();
|
||||
Term::App {
|
||||
callee: Box::new(Term::Var {
|
||||
name: format!("{type_name}.new"),
|
||||
}),
|
||||
args: value_args,
|
||||
tail: false,
|
||||
// #51: honour the written element type-arg. When the
|
||||
// `new` is polymorphic (the call carries a leading
|
||||
// `NewArg::Type`, e.g. `(new RawBuf (con Int) 3)`), the
|
||||
// written type is the binding declaration of the result's
|
||||
// forall var — it must NOT be discarded. We keep
|
||||
// `Term::New` surviving into synth, which binds that var
|
||||
// directly to the written type (`?a := Int`) and pushes
|
||||
// the matching mono observation so codegen mints
|
||||
// `RawBuf_new__Int` (and enforces `param-in` on the
|
||||
// written type). The value-args are desugared in place.
|
||||
//
|
||||
// When the `new` is monomorphic (no `NewArg::Type`, e.g.
|
||||
// `(new Counter 42)`) there is no var to bind, so we keep
|
||||
// the raw-buf.4 lowering to `(app T.new <values…>)` — a
|
||||
// type-scoped call that synth's dotted-name branch
|
||||
// resolves through the TypeDef-first ladder.
|
||||
let has_type_arg = args.iter().any(|a| matches!(a, NewArg::Type(_)));
|
||||
if has_type_arg {
|
||||
let desugared_args: Vec<NewArg> = args
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
NewArg::Value(v) => NewArg::Value(self.desugar_term(v, scope)),
|
||||
NewArg::Type(ty) => NewArg::Type(ty.clone()),
|
||||
})
|
||||
.collect();
|
||||
Term::New {
|
||||
type_name: type_name.clone(),
|
||||
args: desugared_args,
|
||||
}
|
||||
} else {
|
||||
let value_args: Vec<Term> = args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
NewArg::Value(v) => Some(self.desugar_term(v, scope)),
|
||||
NewArg::Type(_) => None,
|
||||
})
|
||||
.collect();
|
||||
Term::App {
|
||||
callee: Box::new(Term::Var {
|
||||
name: format!("{type_name}.new"),
|
||||
}),
|
||||
args: value_args,
|
||||
tail: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
Term::Intrinsic => t.clone(),
|
||||
|
||||
Reference in New Issue
Block a user