Iter 18a: (borrow T) / (own T) mode annotations on fn signatures
Adds form-A surface (borrow T) / (own T) wrappers in fn-type
params and ret slots. Internally these are not new Type variants
but per-position metadata on Type::Fn:
Type::Fn { params, param_modes, ret, ret_mode, effects }
enum ParamMode { Implicit, Own, Borrow }
This keeps Type itself unchanged, so unification, occurs, apply,
and ~190 other Type match-arms in the typechecker need no new
branch. Fields use serde skip-if-default predicates so canonical
JSON hashes for every pre-18a fixture stay bit-identical (sum,
list, hof, closure, list_map, etc.: zero diff under git).
Iter 18a is purely additive: typechecker treats modes as
transparent (mode-compat check deferred to 18c), no codegen
change (--alloc=gc / Boehm path unchanged), no linearity
enforcement. The annotation info flows into the JSON side-table
that 18c will consume.
Equality of mode slices is length-tolerant: a vec![] (the form
written by mechanically-updated construction sites that elide
modes) compares equal to vec![Implicit; n]. This kept the patch
from being a 100-site mechanical migration through the
typechecker. 18c will choose: keep the slack, or normalise to
full-length on construction.
New fixture examples/borrow_own_demo.{ailx,ail.json} exercises
both modes. list_length declares (borrow (con List)); sum_list
declares (own (con List)). Stdout: 3 then 6.
Documentation: DESIGN.md schema-additions block clarified that
modes are Type::Fn metadata (not Type variants); migration plan
flag rename --memory=rc → --alloc=rc to match the existing CLI
flag.
Verification:
- cargo build --workspace green
- cargo test --workspace green (154 tests, +1 vs baseline 153)
- git diff examples/{sum,list,hof,closure,list_map,...}.ail.json: empty
- borrow_own_demo runtime stdout: "3\n6\n"
- canonical JSON contains "param_modes":["borrow"] and ["own"]
This commit is contained in:
@@ -1200,6 +1200,46 @@ fn unreachable_demo() {
|
||||
assert_eq!(lines, vec!["4", "5"]);
|
||||
}
|
||||
|
||||
/// Iter 18a: `(borrow T)` and `(own T)` mode annotations on
|
||||
/// fn-type parameters. Properties protected:
|
||||
/// (1) the parser accepts `(borrow ...)` and `(own ...)` only as
|
||||
/// wrappers in fn-type param/ret slots and round-trips them
|
||||
/// into [`ailang_core::ParamMode`] on `Type::Fn`;
|
||||
/// (2) the canonical-JSON serialisation emits `param_modes` only
|
||||
/// when at least one mode is non-Implicit (verified by reading
|
||||
/// the file and confirming the substring is present), so
|
||||
/// pre-18a fixtures continue to hash bit-identically;
|
||||
/// (3) the typechecker treats modes as transparent — it accepts a
|
||||
/// fn whose parameter is `(borrow (con List))` even when the
|
||||
/// same value is later passed to a fn whose parameter is
|
||||
/// `(own (con List))`, because Iter 18a does not enforce
|
||||
/// mode compatibility (deferred to 18c);
|
||||
/// (4) codegen ignores modes entirely: list_length and sum_list
|
||||
/// compile to the same LLVM IR they would have without the
|
||||
/// wrappers, and the binary prints `3` then `6`.
|
||||
#[test]
|
||||
fn borrow_own_demo_modes_are_metadata_only() {
|
||||
// Sanity-check (2): the on-disk JSON contains `param_modes`
|
||||
// tokens for both fns. If the schema regressed and the field
|
||||
// were dropped, this would fail before the binary even runs.
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||
let json_path = workspace.join("examples").join("borrow_own_demo.ail.json");
|
||||
let json = std::fs::read_to_string(&json_path).expect("read borrow_own_demo.ail.json");
|
||||
assert!(
|
||||
json.contains("\"param_modes\":[\"borrow\"]"),
|
||||
"expected `param_modes:[\"borrow\"]` in canonical JSON; the schema for `(borrow T)` regressed"
|
||||
);
|
||||
assert!(
|
||||
json.contains("\"param_modes\":[\"own\"]"),
|
||||
"expected `param_modes:[\"own\"]` in canonical JSON; the schema for `(own T)` regressed"
|
||||
);
|
||||
|
||||
let stdout = build_and_run("borrow_own_demo.ail.json");
|
||||
let lines: Vec<&str> = stdout.lines().collect();
|
||||
assert_eq!(lines, vec!["3", "6"]);
|
||||
}
|
||||
|
||||
/// Iter 16e: polymorphic `==`. Properties protected:
|
||||
/// (1) `==` typechecks at Int / Bool / Str / Unit (the fixture
|
||||
/// uses every supported case directly via `(app == ...)`);
|
||||
|
||||
Reference in New Issue
Block a user