Iter 10: Term::Seq sequencing operator
Adds `Term::Seq { lhs, rhs }` (serde tag "seq") as a first-class
AST node for sequencing effectful expressions. Equivalent in
behaviour to `let _ = lhs in rhs`, but the dedicated node gives the
pretty-printer and diagnostics a cleaner shape and surfaces the
intent ("run for effect, then yield rhs") to future tooling.
Typecheck: lhs must be Unit; rhs's type is the result; effects
accumulate.
Codegen: lower lhs (drop SSA), lower rhs (return).
Capture / deps walkers: recurse into both sides.
Refactored examples/list_map.ail.json's print_list to use seq
instead of `let _ = ...`. Output unchanged (2/4/6).
Hash stability: existing examples without Term::Seq serialise
bit-identical; only list_map.ail.json's hashes changed (deliberate
refactor).
Tests: 51 green (was 50). New unit test
`ailang_check::tests::seq_lhs_must_be_unit` covers the type-error
path; existing list_map e2e covers the happy path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -797,6 +797,14 @@ fn synth(
|
||||
|
||||
Ok(result_ty.expect("checked arms is non-empty"))
|
||||
}
|
||||
Term::Seq { lhs, rhs } => {
|
||||
// Iter 10: `lhs ; rhs`. lhs must be Unit (its value is
|
||||
// discarded). Effects from both sides accumulate. The
|
||||
// expression's type is rhs's type.
|
||||
let lty = synth(lhs, env, locals, effects, in_def)?;
|
||||
expect_eq(&Type::unit(), <y)?;
|
||||
synth(rhs, env, locals, effects, in_def)
|
||||
}
|
||||
Term::Lam { params, param_tys, ret_ty, effects: lam_effects, body } => {
|
||||
// Iter 8b: a lambda's type is the declared `Type::Fn`. Push
|
||||
// params as locals, check the body's type matches `ret_ty`,
|
||||
@@ -1203,4 +1211,36 @@ mod tests {
|
||||
let err = check(&m).unwrap_err();
|
||||
assert!(format!("{err}").contains("type mismatch"));
|
||||
}
|
||||
|
||||
/// Iter 10: `seq` requires lhs to be Unit. A non-Unit lhs is a
|
||||
/// type error — the value of lhs gets discarded so a useful (non-
|
||||
/// Unit) value would silently vanish.
|
||||
#[test]
|
||||
fn seq_lhs_must_be_unit() {
|
||||
let m = Module {
|
||||
schema: SCHEMA.into(),
|
||||
name: "t".into(),
|
||||
imports: vec![],
|
||||
defs: vec![fn_def(
|
||||
"f",
|
||||
Type::Fn {
|
||||
params: vec![],
|
||||
ret: Box::new(Type::int()),
|
||||
effects: vec![],
|
||||
},
|
||||
vec![],
|
||||
Term::Seq {
|
||||
lhs: Box::new(Term::Lit {
|
||||
lit: Literal::Int { value: 7 },
|
||||
}),
|
||||
rhs: Box::new(Term::Lit {
|
||||
lit: Literal::Int { value: 1 },
|
||||
}),
|
||||
},
|
||||
)],
|
||||
};
|
||||
let err = check(&m).unwrap_err();
|
||||
let msg = format!("{err}");
|
||||
assert!(msg.contains("type mismatch"), "got: {msg}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user