iter ct.1.4-followup: type-con rewrite test + doc clarity

This commit is contained in:
2026-05-11 01:21:18 +02:00
parent d45977f9bb
commit a73f7d3213
2 changed files with 76 additions and 4 deletions
@@ -81,3 +81,64 @@ fn migrate_canonical_types_rewrites_bare_xmod_term_ctor() {
let bytes_after = fs::read(&main_path).unwrap();
assert_eq!(bytes_before, bytes_after, "migration must be idempotent");
}
/// Symmetric to `migrate_canonical_types_rewrites_bare_xmod_term_ctor`,
/// but the only bare cross-module reference in the consumer is a
/// `Type::Con` in a fn return type — the body carries no `Term::Ctor`
/// at all. This pins the Type::Con rewrite branch of `rewrite_def` as
/// a property: a regression that broke that branch (e.g. accidentally
/// scoping `rewrite_type` to only fn params) would leave the Term::Ctor
/// test green and ship the bug.
#[test]
fn migrate_canonical_types_rewrites_bare_xmod_type_con() {
let dir = tmp_dir("rewrites_bare_type_con");
// Defining module: `other` declares `type Foo`.
let other_path = dir.join("other.ail.json");
fs::write(&other_path, serde_json::to_vec_pretty(&serde_json::json!({
"schema": "ailang/v0",
"name": "other",
"imports": [],
"defs": [{ "kind": "type", "name": "Foo", "ctors": [{ "name": "MkFoo", "fields": [] }] }],
})).unwrap()).unwrap();
// Consumer module: imports `other`, body is a unit literal (no
// Term::Ctor) — the only bare cross-module reference is the
// Type::Con `Foo` in the fn return type.
let main_path = dir.join("main.ail.json");
let bare_module = serde_json::json!({
"schema": "ailang/v0",
"name": "main",
"imports": [{ "module": "other" }],
"defs": [{
"kind": "fn", "name": "f",
"type": { "k": "fn", "params": [], "ret": { "k": "con", "name": "Foo" }, "effects": [] },
"params": [],
"body": { "t": "lit", "lit": { "kind": "unit" } }
}],
});
fs::write(&main_path, serde_json::to_vec_pretty(&bare_module).unwrap()).unwrap();
// Run migration.
let status = Command::new(ail_bin())
.args(["migrate-canonical-types", dir.to_str().unwrap()])
.status()
.expect("ail binary must launch");
assert!(status.success(), "migration exited non-zero");
// Inspect rewritten consumer.
let after: serde_json::Value = serde_json::from_slice(
&fs::read(&main_path).unwrap()
).unwrap();
let ret_name = after["defs"][0]["type"]["ret"]["name"].as_str().unwrap();
assert_eq!(ret_name, "other.Foo",
"fn-return Type::Con.name must be qualified after migration");
// Idempotence: run migration again, capture bytes, compare.
let bytes_before = fs::read(&main_path).unwrap();
let status2 = Command::new(ail_bin())
.args(["migrate-canonical-types", dir.to_str().unwrap()])
.status()
.expect("ail binary must launch");
assert!(status2.success(), "second migration exited non-zero");
let bytes_after = fs::read(&main_path).unwrap();
assert_eq!(bytes_before, bytes_after, "migration must be idempotent");
}