diff --git a/crates/ail/src/main.rs b/crates/ail/src/main.rs index db33386..b9ec576 100644 --- a/crates/ail/src/main.rs +++ b/crates/ail/src/main.rs @@ -76,6 +76,10 @@ enum Cmd { /// `render | parse` reproduces the input's canonical bytes /// (gated by `crates/ailang-surface/tests/round_trip.rs`). Render { path: PathBuf }, + /// THROWAWAY (spec 0062): rewrite every bare fn-type slot in a + /// `.ail` file as `(own …)`, preserving explicit `(own)`/`(borrow)`. + /// Removed in the Implicit-deletion cutover. + MigrateModes { path: PathBuf }, /// Prints a single definition as JSON or pretty text. Describe { path: PathBuf, @@ -440,6 +444,22 @@ fn main() -> Result<()> { let m = ailang_surface::load_module(&path)?; print!("{}", ailang_surface::print(&m)); } + Cmd::MigrateModes { path } => { + let src = std::fs::read_to_string(&path)?; + let mut m = ailang_surface::parse(&src)?; + ailang_core::ast::for_each_fn_type_mut(&mut m, &mut |n, pm, rm| { + pm.resize(n, ailang_core::ast::ParamMode::Own); + for x in pm.iter_mut() { + if matches!(x, ailang_core::ast::ParamMode::Implicit) { + *x = ailang_core::ast::ParamMode::Own; + } + } + if matches!(rm, ailang_core::ast::ParamMode::Implicit) { + *rm = ailang_core::ast::ParamMode::Own; + } + }); + std::fs::write(&path, ailang_surface::print(&m))?; + } Cmd::Prose { path } => { // load via `load_module` (single-module mode, // matching how `render` / `parse` work). Workspace-wide diff --git a/crates/ail/tests/migrate_modes.rs b/crates/ail/tests/migrate_modes.rs new file mode 100644 index 0000000..65ef6a3 --- /dev/null +++ b/crates/ail/tests/migrate_modes.rs @@ -0,0 +1,27 @@ +//! Throwaway migration-tool test (spec 0062). Asserts the +//! parse→Implicit↦Own→print pass materialises bare slots as `(own …)` +//! and leaves explicit `(borrow …)` untouched. Deleted in the cutover. +use ailang_surface::{parse, print}; +use ailang_core::ast::ParamMode; + +/// Re-implements the tool's core transform for the unit test so the +/// assertion does not depend on the CLI I/O wrapper. +fn migrate_text(src: &str) -> String { + let mut m = parse(src).expect("parse"); + ailang_core::ast::for_each_fn_type_mut(&mut m, &mut |params_len, pm, rm| { + pm.resize(params_len, ParamMode::Own); + for x in pm.iter_mut() { + if matches!(x, ParamMode::Implicit) { *x = ParamMode::Own; } + } + if matches!(rm, ParamMode::Implicit) { *rm = ParamMode::Own; } + }); + print(&m) +} + +#[test] +fn bare_slot_becomes_own_borrow_preserved() { + let src = "(module m\n (fn f\n (doc \"d\")\n (type (fn-type (params (con Int) (borrow (con Int))) (ret (con Int))))\n (params x y)\n (body x)))\n"; + let out = migrate_text(src); + assert!(out.contains("(params (own (con Int)) (borrow (con Int)))"), "got: {out}"); + assert!(out.contains("(ret (own (con Int)))"), "got: {out}"); +} diff --git a/crates/ailang-core/src/ast.rs b/crates/ailang-core/src/ast.rs index 5eabb5c..ab2c419 100644 --- a/crates/ailang-core/src/ast.rs +++ b/crates/ailang-core/src/ast.rs @@ -846,6 +846,34 @@ impl Type { } } +/// Visit every `Type::Fn` in `m`, letting `f` rewrite its modes. +/// `f(params_len, param_modes, ret_mode)`. Used by the throwaway +/// `migrate-modes` tool (spec 0062); has no other caller and is +/// removed if the migration machinery is retired. +pub fn for_each_fn_type_mut( + m: &mut Module, + f: &mut impl FnMut(usize, &mut Vec, &mut ParamMode), +) { + fn walk_ty(t: &mut Type, f: &mut impl FnMut(usize, &mut Vec, &mut ParamMode)) { + match t { + Type::Fn { params, param_modes, ret, ret_mode, .. } => { + let n = params.len(); + for p in params.iter_mut() { walk_ty(p, f); } + walk_ty(ret, f); + f(n, param_modes, ret_mode); + } + Type::Con { args, .. } => { for a in args.iter_mut() { walk_ty(a, f); } } + Type::Forall { body, .. } => walk_ty(body, f), + Type::Var { .. } => {} + } + } + for def in m.defs.iter_mut() { + if let Def::Fn(fd) = def { + walk_ty(&mut fd.ty, f); + } + } +} + /// Per-parameter / return mode marker on a [`Type::Fn`]. Full /// contract lives in `design/contracts/0008-memory-model.md`. ///