chore: throwaway mode-migration tool for the Implicit cutover (#55, 0121 task 3)
Adds `ail migrate-modes <file>` (a throwaway CLI subcommand) and the AST walker `ailang_core::ast::for_each_fn_type_mut` it drives: parse a .ail, map every bare/Implicit fn-type slot to Own, preserve explicit Own/Borrow, print back. Semantically invisible today (PartialEq treats Implicit == Own), but it materialises the modes so the post-cutover parser — which will reject bare slots — accepts the migrated corpus. Both are throwaway: removed in task 4 once ParamMode::Implicit is deleted (the closure references Implicit and would not compile). RED-first: migrate_modes.rs failed to compile (missing walker) before for_each_fn_type_mut was added. Additive; full workspace suite green. refs #55
This commit is contained in:
@@ -76,6 +76,10 @@ enum Cmd {
|
|||||||
/// `render | parse` reproduces the input's canonical bytes
|
/// `render | parse` reproduces the input's canonical bytes
|
||||||
/// (gated by `crates/ailang-surface/tests/round_trip.rs`).
|
/// (gated by `crates/ailang-surface/tests/round_trip.rs`).
|
||||||
Render { path: PathBuf },
|
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.
|
/// Prints a single definition as JSON or pretty text.
|
||||||
Describe {
|
Describe {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
@@ -440,6 +444,22 @@ fn main() -> Result<()> {
|
|||||||
let m = ailang_surface::load_module(&path)?;
|
let m = ailang_surface::load_module(&path)?;
|
||||||
print!("{}", ailang_surface::print(&m));
|
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 } => {
|
Cmd::Prose { path } => {
|
||||||
// load via `load_module` (single-module mode,
|
// load via `load_module` (single-module mode,
|
||||||
// matching how `render` / `parse` work). Workspace-wide
|
// matching how `render` / `parse` work). Workspace-wide
|
||||||
|
|||||||
@@ -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}");
|
||||||
|
}
|
||||||
@@ -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<ParamMode>, &mut ParamMode),
|
||||||
|
) {
|
||||||
|
fn walk_ty(t: &mut Type, f: &mut impl FnMut(usize, &mut Vec<ParamMode>, &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
|
/// Per-parameter / return mode marker on a [`Type::Fn`]. Full
|
||||||
/// contract lives in `design/contracts/0008-memory-model.md`.
|
/// contract lives in `design/contracts/0008-memory-model.md`.
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user