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:
2026-06-01 16:41:21 +02:00
parent e1c908912b
commit 39b674c1ec
3 changed files with 75 additions and 0 deletions
+20
View File
@@ -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
+27
View File
@@ -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}");
}