test(raw-buf): RED-pin #51 — (new T <elem> …) must honour the element type-arg
Two failing E2E tests pinning the two legs of the #51 codegen crash, both rooted in the `Term::New` desugar dropping `NewArg::Type` (crates/ailang-core/src/desugar.rs:1053): - Leg 1 (legitimate): `(new RawBuf (con Int) 3)` used only via `RawBuf.size` — never observed by get/set, so the dropped `(con Int)` is the sole element-type carrier. The unpinned forall var defaults to Unit in mono and `ail build --alloc=rc` aborts on the unregistered `RawBuf_new__Unit` intercept. Pins build+run printing `3`. - Leg 2 (forbidden): `(new RawBuf (con Unit) 3)` — Unit is outside the param-in set {Int, Float, Bool}, but the dropped type-arg never reaches param-in enforcement, so `ail check` wrongly passes. Pins a `param-not-in-restricted-set` rejection naming Unit at check time. Both RED at HEAD. GREEN side carries the explicit element type from Term::New through to monomorphisation (must not mint __Unit) and adds the leg-2 reject pre-desugar per the Term::New/desugar lockstep. refs #51
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
//! RED-pin for #51 (raw-buf fieldtest F7): the explicit element
|
||||||
|
//! type-arg of `(new RawBuf <elem> <size>)` must be honoured.
|
||||||
|
//!
|
||||||
|
//! Property protected: the author's `(con Elem)` annotation on a
|
||||||
|
//! `(new RawBuf ...)` is the binding declaration of the buffer's
|
||||||
|
//! element type — it must NOT depend on a downstream `get`/`set` to
|
||||||
|
//! be recovered, and it must be subject to RawBuf's `param-in`
|
||||||
|
//! restricted set {Int, Float, Bool} at check time.
|
||||||
|
//!
|
||||||
|
//! Two legs, both stemming from the same defect: `(new T <type>
|
||||||
|
//! <values>)` desugars to `(app T.new <values>)` and DROPS the
|
||||||
|
//! leading `NewArg::Type` (crates/ailang-core/src/desugar.rs:1053).
|
||||||
|
//!
|
||||||
|
//! - Leg 1 (legitimate, must build+run): `(new RawBuf (con Int) 3)`
|
||||||
|
//! used only via `RawBuf.size`. With the type-arg dropped, the
|
||||||
|
//! result's forall var `a` is never pinned by use, monomorph
|
||||||
|
//! defaults it to Unit (mono.rs free-fn-call arm / subst.rs), and
|
||||||
|
//! codegen mints `RawBuf_new__Unit`, which has no registered
|
||||||
|
//! intercept (intercepts.rs registers only Int/Float/Bool). Today
|
||||||
|
//! `ail build --alloc=rc` aborts with
|
||||||
|
//! `intrinsic fn RawBuf_new__Unit has no registered intercept`.
|
||||||
|
//! Expected post-fix: build succeeds, program prints `3`.
|
||||||
|
//!
|
||||||
|
//! - Leg 2 (forbidden, must reject at check): `(new RawBuf (con
|
||||||
|
//! Unit) 3)`. Unit is not in RawBuf's param-in set, but because
|
||||||
|
//! the `(con Unit)` type-arg is dropped before check, the
|
||||||
|
//! `param-in` enforcement in `check_type_well_formed`
|
||||||
|
//! (crates/ailang-check/src/lib.rs:2084) never sees a `RawBuf<Unit>`
|
||||||
|
//! type, so `ail check` wrongly passes (exit 0) and the program
|
||||||
|
//! crashes at `ail build` with the same `RawBuf_new__Unit` error.
|
||||||
|
//! Expected post-fix: `ail check` fails with
|
||||||
|
//! `param-not-in-restricted-set` naming `Unit`; codegen unreached.
|
||||||
|
//!
|
||||||
|
//! These are CLI-boundary E2E tests because the defect spans the
|
||||||
|
//! desugar -> check/codegen pipeline and the symptoms are the exit
|
||||||
|
//! behaviour of `ail check` / `ail build` themselves.
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn repo_root() -> PathBuf {
|
||||||
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
|
Path::new(manifest_dir)
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Leg 1: a `(new RawBuf (con Int) 3)` buffer used only via
|
||||||
|
/// `RawBuf.size` must build under `--alloc=rc` and print its size.
|
||||||
|
#[test]
|
||||||
|
fn rawbuf_new_int_size_only_builds_and_runs() {
|
||||||
|
let fixture = repo_root().join("examples/raw_buf_new_type_arg_intsize.ail");
|
||||||
|
let tmp = std::env::temp_dir().join(format!(
|
||||||
|
"ailang_rawbuf_new_type_arg_intsize_{}",
|
||||||
|
std::process::id()
|
||||||
|
));
|
||||||
|
std::fs::create_dir_all(&tmp).unwrap();
|
||||||
|
let out = tmp.join("bin");
|
||||||
|
|
||||||
|
let build = Command::new(env!("CARGO_BIN_EXE_ail"))
|
||||||
|
.args(["build", fixture.to_str().unwrap(), "--alloc=rc", "-o"])
|
||||||
|
.arg(&out)
|
||||||
|
.output()
|
||||||
|
.expect("ail build failed to spawn");
|
||||||
|
assert!(
|
||||||
|
build.status.success(),
|
||||||
|
"ail build --alloc=rc failed for size-only RawBuf<Int>: stderr={}",
|
||||||
|
String::from_utf8_lossy(&build.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
let run = Command::new(&out).output().expect("execute binary");
|
||||||
|
assert!(
|
||||||
|
run.status.success(),
|
||||||
|
"binary exited non-zero: {:?}",
|
||||||
|
run.status
|
||||||
|
);
|
||||||
|
let stdout = String::from_utf8_lossy(&run.stdout);
|
||||||
|
assert!(
|
||||||
|
stdout.trim() == "3",
|
||||||
|
"expected the program to print the buffer size `3`, got: {stdout:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Leg 2: a `(new RawBuf (con Unit) 3)` names a forbidden element
|
||||||
|
/// type and must be rejected at `ail check` with
|
||||||
|
/// `param-not-in-restricted-set` naming `Unit`.
|
||||||
|
#[test]
|
||||||
|
fn rawbuf_new_unit_element_rejected_at_check() {
|
||||||
|
let fixture = repo_root().join("examples/raw_buf_new_type_arg_unit_reject.ail");
|
||||||
|
|
||||||
|
let out = Command::new(env!("CARGO_BIN_EXE_ail"))
|
||||||
|
.arg("check")
|
||||||
|
.arg(&fixture)
|
||||||
|
.output()
|
||||||
|
.expect("ail check failed to spawn");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!out.status.success(),
|
||||||
|
"expected `ail check` to REJECT RawBuf<Unit>, but it passed: stdout={}",
|
||||||
|
String::from_utf8_lossy(&out.stdout)
|
||||||
|
);
|
||||||
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("param-not-in-restricted-set"),
|
||||||
|
"expected diagnostic code `param-not-in-restricted-set`, got: {stderr}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("Unit"),
|
||||||
|
"expected the diagnostic to name the forbidden element `Unit`, got: {stderr}"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
(module raw_buf_new_type_arg_intsize
|
||||||
|
(fn main
|
||||||
|
(doc "RED-pin fixture for #51 leg 1 (raw-buf F7). A `(new RawBuf (con Int) 3)` buffer used ONLY via `RawBuf.size` — its element type is never observed by a later get/set, so the only carrier of the element type is the author's explicit `(con Int)` type-arg on `new`. Today `(new T <type> <values>)` desugars to `(app T.new <values>)` and DROPS the leading NewArg::Type (crates/ailang-core/src/desugar.rs:1053), so the result's forall var `a` is unpinned and monomorphisation defaults it to Unit (crates/ailang-check/src/mono.rs free-fn-call arm; mirror crates/ailang-codegen/src/subst.rs). Codegen then mints `RawBuf_new__Unit`, which has no registered intercept (only Int/Float/Bool are in crates/ailang-codegen/src/intercepts.rs) and `ail build --alloc=rc` aborts: `intrinsic fn RawBuf_new__Unit has no registered intercept`. Expected post-fix: the explicit `(con Int)` is carried through monomorphisation so the buffer specialises to `RawBuf_new__Int`; build succeeds and the program prints 3 (the size).")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body
|
||||||
|
(let b (new RawBuf (con Int) 3)
|
||||||
|
(app print (app RawBuf.size b))))))
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
(module raw_buf_new_type_arg_unit_reject
|
||||||
|
(fn main
|
||||||
|
(doc "RED-pin fixture for #51 leg 2 (raw-buf F7). A `(new RawBuf (con Unit) 3)` names Unit as the element type — but Unit is NOT in RawBuf's param-in restricted set {Int, Float, Bool}, so this must be REJECTED at `ail check` with `param-not-in-restricted-set` naming Unit. Today the `(new T <type> <values>)` desugar (crates/ailang-core/src/desugar.rs:1053) drops the `NewArg::Type` BEFORE check, so the `(con Unit)` element annotation never reaches the param-in enforcement in check_type_well_formed (crates/ailang-check/src/lib.rs:2084). `ail check` therefore passes (exit 0) and the program only crashes later at `ail build` with the same unregistered `RawBuf_new__Unit` intercept as leg 1. Expected post-fix: a pre-desugar reject on the Term::New shape (per the CLAUDE.md Term::New/desugar lockstep, crates/ailang-check/src/pre_desugar_validation.rs) fails `ail check` with `param-not-in-restricted-set` naming Unit; codegen is never reached.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body
|
||||||
|
(let b (new RawBuf (con Unit) 3)
|
||||||
|
(app print (app RawBuf.size b))))))
|
||||||
Reference in New Issue
Block a user