iter 23.4-prep.1: linearity — register Def::Class method types in globals

This commit is contained in:
2026-05-11 14:54:49 +02:00
parent c3e8044361
commit 0caaced7e8
+85 -5
View File
@@ -205,11 +205,19 @@ pub(crate) fn check_module(m: &Module) -> Vec<Diagnostic> {
ctors.insert(name.clone(), fields.clone());
}
}
// Iter 22b.1: class/instance defs are skipped here. Once
// 22b.3 monomorphisation materialises class methods as
// ordinary `FnDef`s, the lift's globals map will pick
// them up automatically.
Def::Class(_) | Def::Instance(_) => {}
// Iter 23.4-prep: class methods register their types into
// the globals map so `callee_arg_modes` can see their
// `param_modes`. Without this, a borrow-mode fn that
// forwards its borrow params into a class-method call
// fires `consume-while-borrowed` false positives.
// Instance defs stay a no-op: instance method bodies are
// walked separately via `Def::Fn` after monomorphisation.
Def::Class(c) => {
for m in &c.methods {
globals.insert(m.name.clone(), strip_forall(&m.ty).clone());
}
}
Def::Instance(_) => {}
}
}
@@ -1426,4 +1434,76 @@ mod tests {
})
);
}
/// Iter 23.4-prep Task 1: a borrow-mode fn that forwards its borrow
/// params into a class-method call must not fire
/// `consume-while-borrowed` false positives. The pre-fix behaviour
/// is two diagnostics (one per param) because
/// `linearity::check_module`'s globals-construction loop skips
/// `Def::Class`, so `callee_arg_modes` cannot see the method's
/// `param_modes` and defaults each call argument to `Consume`.
#[test]
fn class_method_borrow_call_does_not_fire_consume_while_borrowed() {
use ailang_core::ast::{ClassDef, ClassMethod};
let class_eq = Def::Class(ClassDef {
name: "TestEq".into(),
param: "a".into(),
superclass: None,
methods: vec![ClassMethod {
name: "teq".into(),
ty: Type::Fn {
params: vec![
Type::Var { name: "a".into() },
Type::Var { name: "a".into() },
],
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
ret: Box::new(Type::bool_()),
ret_mode: ParamMode::Implicit,
effects: vec![],
},
default: None,
}],
doc: None,
});
let ne = Def::Fn(FnDef {
name: "tne".into(),
ty: Type::Fn {
params: vec![
Type::Var { name: "a".into() },
Type::Var { name: "a".into() },
],
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
ret: Box::new(Type::bool_()),
ret_mode: ParamMode::Implicit,
effects: vec![],
},
params: vec!["x".into(), "y".into()],
body: Term::App {
callee: Box::new(Term::Var { name: "teq".into() }),
args: vec![
Term::Var { name: "x".into() },
Term::Var { name: "y".into() },
],
tail: false,
},
suppress: vec![],
doc: None,
});
let m = Module {
schema: ailang_core::SCHEMA.into(),
name: "m".into(),
imports: vec![],
defs: vec![class_eq, ne],
};
let diags = check_module(&m);
assert!(
diags.is_empty(),
"expected no diagnostics but got {:#?}",
diags
);
}
}