Iter 18c.2: linearity check + suggested_rewrites
Adds a per-fn linearity check that runs on every fn whose param_modes are all explicit (Borrow or Own, no Implicit), tracks each binder's consume/borrow state through the AST, and emits two diagnostics with form-A `suggested_rewrites`: - `use-after-consume` — a binder is referenced after it has already been consumed. - `consume-while-borrowed` — a binder is consumed while a borrow of it is still live (sibling arg slot, or an enclosing-fn Borrow param). Fns with any Implicit param skip the check entirely; that's the back-compat lane that keeps every existing fixture green. Pure diagnostic addition: no IR change, no codegen change, no runtime change. Each diagnostic carries a non-empty `suggested_rewrites` whose `replacement` is form-A AILang text (typically `(clone <name>)`). Adds two new public entrypoints to ailang-surface — `parse_term` and `term_to_form_a` — so the round-trip `parse_term(term_to_form_a(t)) ≡ t` is the contract every emitted replacement must satisfy. Tests assert the contract. Linearity pass runs only on modules that typechecked clean (any upstream typecheck error suppresses it for that module — running on partly-defined IR would produce noise). Tests: 3 unit tests in `linearity::tests`, 3 integration tests in `ailang-check/tests/workspace.rs`, 1 serialisation test in `diagnostic.rs`. `cargo test --workspace` green.
This commit is contained in:
@@ -130,6 +130,29 @@ pub fn parse(input: &str) -> Result<Module, ParseError> {
|
||||
Ok(m)
|
||||
}
|
||||
|
||||
/// Parse a single form-A term — the concrete-syntax counterpart of
|
||||
/// [`Term`]. This is the dual of [`crate::print::term_to_form_a`] and is
|
||||
/// used by callers that produce form-A snippets out-of-band, e.g. the
|
||||
/// `suggested_rewrites` payload of `ail check --json` (Iter 18c.2). The
|
||||
/// input must consume to EOF after the term — extra trailing tokens
|
||||
/// produce [`ParseError::Unexpected`].
|
||||
///
|
||||
/// Round-trip: `parse_term(term_to_form_a(t)) ≡ t` for every term `t`
|
||||
/// the surface can express.
|
||||
pub fn parse_term(input: &str) -> Result<Term, ParseError> {
|
||||
let toks = tokenize(input)?;
|
||||
let mut p = Parser::new(&toks);
|
||||
let t = p.parse_term()?;
|
||||
if p.cur < p.toks.len() {
|
||||
return Err(ParseError::Unexpected {
|
||||
expected: "end of input".into(),
|
||||
got: tok_label(&p.toks[p.cur].tok),
|
||||
pos: p.toks[p.cur].span.start,
|
||||
});
|
||||
}
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
fn tok_label(t: &Tok) -> String {
|
||||
match t {
|
||||
Tok::LParen => "`(`".into(),
|
||||
|
||||
Reference in New Issue
Block a user