|
|
|
@@ -696,6 +696,32 @@ pub enum CheckError {
|
|
|
|
|
#[error("mut-var `{name}` cannot be captured by a lambda — mut-vars are alloca-resident and do not escape their enclosing mut block. Either move the lambda outside the mut block, or restructure to pass `{name}` as a lambda parameter (deferring escape to a future milestone with ref-types and the !Mut effect).")]
|
|
|
|
|
MutVarCapturedByLambda { name: String },
|
|
|
|
|
|
|
|
|
|
/// loop-recur iter 2: `Term::Recur` reached typecheck with no
|
|
|
|
|
/// lexically-enclosing `Term::Loop` (the `loop_stack` was empty).
|
|
|
|
|
#[error("`recur` is only valid inside a `loop` — there is no enclosing loop here")]
|
|
|
|
|
RecurOutsideLoop,
|
|
|
|
|
|
|
|
|
|
/// loop-recur iter 2: `Term::Recur`'s argument count differs from
|
|
|
|
|
/// the enclosing loop's binder count. `recur` rebinds every loop
|
|
|
|
|
/// binder positionally, so the counts must match exactly.
|
|
|
|
|
#[error("`recur` passes {got} argument(s) but the enclosing loop has {expected} binder(s) — recur must rebind every binder positionally")]
|
|
|
|
|
RecurArityMismatch { expected: usize, got: usize },
|
|
|
|
|
|
|
|
|
|
/// loop-recur iter 2: a `recur` argument's type differs from the
|
|
|
|
|
/// corresponding loop binder's declared type (0-based position).
|
|
|
|
|
#[error("`recur` argument {position} has type `{got}` but the enclosing loop's binder at that position is `{expected}` — every recur argument must match its binder's type")]
|
|
|
|
|
RecurTypeMismatch {
|
|
|
|
|
position: usize,
|
|
|
|
|
expected: String,
|
|
|
|
|
got: String,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/// loop-recur iter 2: `Term::Recur` appears somewhere other than
|
|
|
|
|
/// the tail position of its enclosing loop body. `recur` is a
|
|
|
|
|
/// structural back-jump; it cannot be a sub-expression.
|
|
|
|
|
#[error("`recur` must be in tail position of its enclosing `loop` body — it is a back-jump, not a value-producing sub-expression")]
|
|
|
|
|
RecurNotInTailPosition,
|
|
|
|
|
|
|
|
|
|
/// Iter 22b.3: an internal invariant in the typechecker / mono pass
|
|
|
|
|
/// was violated — surfaced as an error so callers can propagate
|
|
|
|
|
/// rather than abort, but in well-formed inputs (typecheck has
|
|
|
|
@@ -748,6 +774,10 @@ impl CheckError {
|
|
|
|
|
CheckError::AssignTypeMismatch { .. } => "assign-type-mismatch",
|
|
|
|
|
CheckError::UnsupportedMutVarType { .. } => "mut-var-unsupported-type",
|
|
|
|
|
CheckError::MutVarCapturedByLambda { .. } => "mut-var-captured-by-lambda",
|
|
|
|
|
CheckError::RecurOutsideLoop => "recur-outside-loop",
|
|
|
|
|
CheckError::RecurArityMismatch { .. } => "recur-arity-mismatch",
|
|
|
|
|
CheckError::RecurTypeMismatch { .. } => "recur-type-mismatch",
|
|
|
|
|
CheckError::RecurNotInTailPosition => "recur-not-in-tail-position",
|
|
|
|
|
CheckError::Internal(_) => "internal",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -822,6 +852,12 @@ impl CheckError {
|
|
|
|
|
CheckError::MutVarCapturedByLambda { name } => {
|
|
|
|
|
serde_json::json!({"name": name})
|
|
|
|
|
}
|
|
|
|
|
CheckError::RecurArityMismatch { expected, got } => {
|
|
|
|
|
serde_json::json!({"expected": expected, "actual": got})
|
|
|
|
|
}
|
|
|
|
|
CheckError::RecurTypeMismatch { position, expected, got } => {
|
|
|
|
|
serde_json::json!({"position": position, "expected": expected, "actual": got})
|
|
|
|
|
}
|
|
|
|
|
_ => serde_json::Value::Object(serde_json::Map::new()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -1967,7 +2003,11 @@ fn check_fn(f: &FnDef, env: &Env, out_warnings: &mut Vec<Diagnostic>) -> Result<
|
|
|
|
|
// pushed/popped by `Term::Mut` arms during the walk; discarded after
|
|
|
|
|
// the body type-checks.
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
let body_ty = synth(&f.body, &env, &mut locals, &mut mut_scope_stack, &mut effects, &f.name, &mut subst, &mut counter, &mut residuals, &mut free_fn_calls, &mut warnings)?;
|
|
|
|
|
// loop-recur iter 2: fresh per-fn-body loop-binder-type stack.
|
|
|
|
|
// Empty at fn entry; pushed/popped by `Term::Loop` arms during
|
|
|
|
|
// the synth walk; discarded after the body type-checks.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let body_ty = synth(&f.body, &env, &mut locals, &mut mut_scope_stack, &mut loop_stack, &mut effects, &f.name, &mut subst, &mut counter, &mut residuals, &mut free_fn_calls, &mut warnings)?;
|
|
|
|
|
unify(&ret_ty, &body_ty, &mut subst)?;
|
|
|
|
|
// mq.3: surface synth-time warnings into the caller-supplied
|
|
|
|
|
// accumulator. The warnings already carry `def: Some(f.name)`
|
|
|
|
@@ -1979,6 +2019,12 @@ fn check_fn(f: &FnDef, env: &Env, out_warnings: &mut Vec<Diagnostic>) -> Result<
|
|
|
|
|
// call doesn't drown out the underlying type error.
|
|
|
|
|
verify_tail_positions(&f.body, true)?;
|
|
|
|
|
|
|
|
|
|
// loop-recur iter 2: recur-in-tail-position verification. Runs
|
|
|
|
|
// after synth (so RecurOutsideLoop/arity/type take code-
|
|
|
|
|
// precedence) and alongside verify_tail_positions (sibling, not
|
|
|
|
|
// a repurpose — verify_tail_positions' tail-app role is frozen).
|
|
|
|
|
verify_loop_body(&f.body, false)?;
|
|
|
|
|
|
|
|
|
|
let declared: BTreeSet<String> = declared_effs.into_iter().collect();
|
|
|
|
|
for e in &effects {
|
|
|
|
|
if !declared.contains(e) {
|
|
|
|
@@ -2724,6 +2770,106 @@ pub fn verify_tail_positions(t: &Term, is_tail: bool) -> Result<()> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// loop-recur iter 2: enforces that every `Term::Recur` occurs in
|
|
|
|
|
/// tail position of its lexically-innermost enclosing `Term::Loop`
|
|
|
|
|
/// body. A NEW private pass — a *sibling* of
|
|
|
|
|
/// `verify_tail_positions` (which is spec-frozen for its tail-app
|
|
|
|
|
/// role and unchanged). Invoked from `check_fn` *after* `synth`, so
|
|
|
|
|
/// `RecurOutsideLoop`/arity/type (raised in `synth`) take code-
|
|
|
|
|
/// precedence; by the time a `Recur` is reached here it is known to
|
|
|
|
|
/// be inside a loop with matching arity/types — this pass adds only
|
|
|
|
|
/// the tail-position rule. `in_loop_tail` is the loop analogue of
|
|
|
|
|
/// `verify_tail_positions`' `is_tail`, scoped to the innermost loop.
|
|
|
|
|
fn verify_loop_body(t: &Term, in_loop_tail: bool) -> Result<()> {
|
|
|
|
|
match t {
|
|
|
|
|
Term::Lit { .. } | Term::Var { .. } => Ok(()),
|
|
|
|
|
Term::Recur { args } => {
|
|
|
|
|
if !in_loop_tail {
|
|
|
|
|
return Err(CheckError::RecurNotInTailPosition);
|
|
|
|
|
}
|
|
|
|
|
// recur args are evaluated in non-tail position; a recur
|
|
|
|
|
// nested inside a recur arg is itself non-tail.
|
|
|
|
|
for a in args {
|
|
|
|
|
verify_loop_body(a, false)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Term::Loop { binders, body } => {
|
|
|
|
|
// Binder inits run once on loop entry — not in the
|
|
|
|
|
// loop's tail scope. The loop body opens a FRESH
|
|
|
|
|
// innermost-loop tail scope (independent of where the
|
|
|
|
|
// loop itself sits — the loop's tail scope is intrinsic).
|
|
|
|
|
for b in binders {
|
|
|
|
|
verify_loop_body(&b.init, false)?;
|
|
|
|
|
}
|
|
|
|
|
verify_loop_body(body, true)
|
|
|
|
|
}
|
|
|
|
|
Term::App { callee, args, .. } => {
|
|
|
|
|
verify_loop_body(callee, false)?;
|
|
|
|
|
for a in args {
|
|
|
|
|
verify_loop_body(a, false)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Term::Do { args, .. } => {
|
|
|
|
|
for a in args {
|
|
|
|
|
verify_loop_body(a, false)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Term::Let { value, body, .. } => {
|
|
|
|
|
verify_loop_body(value, false)?;
|
|
|
|
|
verify_loop_body(body, in_loop_tail)
|
|
|
|
|
}
|
|
|
|
|
Term::If { cond, then, else_ } => {
|
|
|
|
|
verify_loop_body(cond, false)?;
|
|
|
|
|
verify_loop_body(then, in_loop_tail)?;
|
|
|
|
|
verify_loop_body(else_, in_loop_tail)
|
|
|
|
|
}
|
|
|
|
|
Term::Seq { lhs, rhs } => {
|
|
|
|
|
verify_loop_body(lhs, false)?;
|
|
|
|
|
verify_loop_body(rhs, in_loop_tail)
|
|
|
|
|
}
|
|
|
|
|
Term::Match { scrutinee, arms } => {
|
|
|
|
|
verify_loop_body(scrutinee, false)?;
|
|
|
|
|
for arm in arms {
|
|
|
|
|
verify_loop_body(&arm.body, in_loop_tail)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Term::Ctor { args, .. } => {
|
|
|
|
|
for a in args {
|
|
|
|
|
verify_loop_body(a, false)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Term::Lam { body, .. } => {
|
|
|
|
|
// A `recur` cannot cross a lambda boundary (different
|
|
|
|
|
// control frame). Reset: a recur inside a lam is outside
|
|
|
|
|
// every enclosing loop's tail scope — and synth's
|
|
|
|
|
// loop_stack likewise does not cross the lam, so such a
|
|
|
|
|
// recur is already a synth `RecurOutsideLoop`.
|
|
|
|
|
verify_loop_body(body, false)
|
|
|
|
|
}
|
|
|
|
|
Term::LetRec { body, in_term, .. } => {
|
|
|
|
|
verify_loop_body(body, false)?;
|
|
|
|
|
verify_loop_body(in_term, in_loop_tail)
|
|
|
|
|
}
|
|
|
|
|
Term::Clone { value } => verify_loop_body(value, in_loop_tail),
|
|
|
|
|
Term::ReuseAs { source, body } => {
|
|
|
|
|
verify_loop_body(source, false)?;
|
|
|
|
|
verify_loop_body(body, in_loop_tail)
|
|
|
|
|
}
|
|
|
|
|
Term::Mut { vars, body } => {
|
|
|
|
|
for v in vars {
|
|
|
|
|
verify_loop_body(&v.init, false)?;
|
|
|
|
|
}
|
|
|
|
|
verify_loop_body(body, in_loop_tail)
|
|
|
|
|
}
|
|
|
|
|
Term::Assign { value, .. } => verify_loop_body(value, false),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check_const(c: &ConstDef, env: &Env, out_warnings: &mut Vec<Diagnostic>) -> Result<()> {
|
|
|
|
|
// Const types are never polymorphic — a Forall here is rejected
|
|
|
|
|
// outright. Any other type passes through to `synth` as before.
|
|
|
|
@@ -2745,7 +2891,10 @@ fn check_const(c: &ConstDef, env: &Env, out_warnings: &mut Vec<Diagnostic>) -> R
|
|
|
|
|
// at this entry point — `Term::Mut` may appear inside a const body
|
|
|
|
|
// and push frames as the walk descends).
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
let v = synth(&c.value, env, &mut locals, &mut mut_scope_stack, &mut effects, &c.name, &mut subst, &mut counter, &mut residuals, &mut free_fn_calls, &mut warnings)?;
|
|
|
|
|
// loop-recur iter 2: a const value has no enclosing loop; any
|
|
|
|
|
// `recur` in a const body correctly fires `RecurOutsideLoop`.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let v = synth(&c.value, env, &mut locals, &mut mut_scope_stack, &mut loop_stack, &mut effects, &c.name, &mut subst, &mut counter, &mut residuals, &mut free_fn_calls, &mut warnings)?;
|
|
|
|
|
out_warnings.extend(warnings);
|
|
|
|
|
unify(&c.ty, &v, &mut subst)?;
|
|
|
|
|
if !effects.is_empty() {
|
|
|
|
@@ -2770,6 +2919,15 @@ pub(crate) fn synth(
|
|
|
|
|
// name through it. Position: locals-adjacent because both are
|
|
|
|
|
// per-fn-body lexical scope state.
|
|
|
|
|
mut_scope_stack: &mut Vec<IndexMap<String, Type>>,
|
|
|
|
|
// loop-recur iter 2: per-walk loop-binder-type stack. Each frame
|
|
|
|
|
// is one `Term::Loop`'s binder types in declaration order
|
|
|
|
|
// (positional — `recur` rebinds binders by position, not name,
|
|
|
|
|
// so this is `Vec<Type>` not name-keyed like `mut_scope_stack`).
|
|
|
|
|
// Pushed on `Term::Loop` body entry, popped on exit; consulted
|
|
|
|
|
// innermost-first by `Term::Recur` for arity + per-position type.
|
|
|
|
|
// Position: mut_scope_stack-adjacent — both are per-fn-body
|
|
|
|
|
// lexical-control scope state threaded identically.
|
|
|
|
|
loop_stack: &mut Vec<Vec<Type>>,
|
|
|
|
|
effects: &mut BTreeSet<String>,
|
|
|
|
|
in_def: &str,
|
|
|
|
|
subst: &mut Subst,
|
|
|
|
@@ -3158,7 +3316,7 @@ pub(crate) fn synth(
|
|
|
|
|
Ok(maybe_instantiate(raw, counter))
|
|
|
|
|
}
|
|
|
|
|
Term::App { callee, args, .. } => {
|
|
|
|
|
let cty = synth(callee, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let cty = synth(callee, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let cty = subst.apply(&cty);
|
|
|
|
|
let (params, ret, fx) = match &cty {
|
|
|
|
|
Type::Fn { params, ret, effects: fx, .. } => {
|
|
|
|
@@ -3194,7 +3352,7 @@ pub(crate) fn synth(
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
for (a, exp) in args.iter().zip(params.iter()) {
|
|
|
|
|
let actual = synth(a, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let actual = synth(a, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
unify(exp, &actual, subst)?;
|
|
|
|
|
}
|
|
|
|
|
for e in fx {
|
|
|
|
@@ -3203,9 +3361,9 @@ pub(crate) fn synth(
|
|
|
|
|
Ok(subst.apply(&ret))
|
|
|
|
|
}
|
|
|
|
|
Term::Let { name, value, body } => {
|
|
|
|
|
let v = synth(value, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let v = synth(value, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let prev = locals.insert(name.clone(), v);
|
|
|
|
|
let r = synth(body, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let r = synth(body, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
match prev {
|
|
|
|
|
Some(p) => {
|
|
|
|
|
locals.insert(name.clone(), p);
|
|
|
|
@@ -3217,10 +3375,10 @@ pub(crate) fn synth(
|
|
|
|
|
Ok(r)
|
|
|
|
|
}
|
|
|
|
|
Term::If { cond, then, else_ } => {
|
|
|
|
|
let c = synth(cond, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let c = synth(cond, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
unify(&Type::bool_(), &c, subst)?;
|
|
|
|
|
let t1 = synth(then, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let t2 = synth(else_, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let t1 = synth(then, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let t2 = synth(else_, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
unify(&t1, &t2, subst)?;
|
|
|
|
|
Ok(subst.apply(&t1))
|
|
|
|
|
}
|
|
|
|
@@ -3238,7 +3396,7 @@ pub(crate) fn synth(
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
for (a, exp) in args.iter().zip(sig.params.iter()) {
|
|
|
|
|
let actual = synth(a, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let actual = synth(a, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
unify(exp, &actual, subst)?;
|
|
|
|
|
}
|
|
|
|
|
effects.insert(sig.effect.clone());
|
|
|
|
@@ -3333,7 +3491,7 @@ pub(crate) fn synth(
|
|
|
|
|
}
|
|
|
|
|
for (a, exp) in args.iter().zip(qualified_fields.iter()) {
|
|
|
|
|
let exp_inst = substitute_rigids(exp, &mapping);
|
|
|
|
|
let actual = synth(a, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let actual = synth(a, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
unify(&exp_inst, &actual, subst)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(Type::Con {
|
|
|
|
@@ -3342,7 +3500,7 @@ pub(crate) fn synth(
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
Term::Match { scrutinee, arms } => {
|
|
|
|
|
let s_ty = synth(scrutinee, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let s_ty = synth(scrutinee, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
if arms.is_empty() {
|
|
|
|
|
return Err(CheckError::NonExhaustive {
|
|
|
|
|
ty: ailang_core::pretty::type_to_string(&s_ty),
|
|
|
|
@@ -3360,7 +3518,7 @@ pub(crate) fn synth(
|
|
|
|
|
let prev = locals.insert(n.clone(), t.clone());
|
|
|
|
|
pushed.push((n.clone(), prev));
|
|
|
|
|
}
|
|
|
|
|
let body_ty = synth(&arm.body, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let body_ty = synth(&arm.body, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
for (n, prev) in pushed.into_iter().rev() {
|
|
|
|
|
match prev {
|
|
|
|
|
Some(p) => {
|
|
|
|
@@ -3435,9 +3593,9 @@ pub(crate) fn synth(
|
|
|
|
|
Ok(subst.apply(&result_ty.expect("checked arms is non-empty")))
|
|
|
|
|
}
|
|
|
|
|
Term::Seq { lhs, rhs } => {
|
|
|
|
|
let lty = synth(lhs, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let lty = synth(lhs, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
unify(&Type::unit(), <y, subst)?;
|
|
|
|
|
synth(rhs, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)
|
|
|
|
|
synth(rhs, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)
|
|
|
|
|
}
|
|
|
|
|
Term::Lam { params, param_tys, ret_ty, effects: lam_effects, body } => {
|
|
|
|
|
// Iter mut.4-tidy: reject lambda-captures-of-mut-var.
|
|
|
|
@@ -3471,7 +3629,7 @@ pub(crate) fn synth(
|
|
|
|
|
pushed.push((n.clone(), prev));
|
|
|
|
|
}
|
|
|
|
|
let mut body_effects: BTreeSet<String> = BTreeSet::new();
|
|
|
|
|
let body_ty = synth(body, env, locals, mut_scope_stack, &mut body_effects, in_def, subst, counter, residuals, free_fn_calls, warnings);
|
|
|
|
|
let body_ty = synth(body, env, locals, mut_scope_stack, loop_stack, &mut body_effects, in_def, subst, counter, residuals, free_fn_calls, warnings);
|
|
|
|
|
for (n, prev) in pushed.into_iter().rev() {
|
|
|
|
|
match prev {
|
|
|
|
|
Some(p) => {
|
|
|
|
@@ -3559,7 +3717,7 @@ pub(crate) fn synth(
|
|
|
|
|
// subset rule against `declared_effs` — exactly like
|
|
|
|
|
// `Term::Lam`.
|
|
|
|
|
let mut body_effects: BTreeSet<String> = BTreeSet::new();
|
|
|
|
|
let body_ty = synth(body, env, locals, mut_scope_stack, &mut body_effects, in_def, subst, counter, residuals, free_fn_calls, warnings);
|
|
|
|
|
let body_ty = synth(body, env, locals, mut_scope_stack, loop_stack, &mut body_effects, in_def, subst, counter, residuals, free_fn_calls, warnings);
|
|
|
|
|
|
|
|
|
|
// Restore body-scope locals (params + name).
|
|
|
|
|
for (n, prev) in pushed.into_iter().rev() {
|
|
|
|
@@ -3586,7 +3744,7 @@ pub(crate) fn synth(
|
|
|
|
|
// scope (params are not visible here; only the recursive
|
|
|
|
|
// binding is).
|
|
|
|
|
let prev_in = locals.insert(name.clone(), ty.clone());
|
|
|
|
|
let in_ty = synth(in_term, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings);
|
|
|
|
|
let in_ty = synth(in_term, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings);
|
|
|
|
|
match prev_in {
|
|
|
|
|
Some(p) => {
|
|
|
|
|
locals.insert(name.clone(), p);
|
|
|
|
@@ -3602,7 +3760,7 @@ pub(crate) fn synth(
|
|
|
|
|
// No constraint generated, no environment change. The
|
|
|
|
|
// wrapper records author intent for the future RC inc/dec
|
|
|
|
|
// emission pass (18c.3); typing is pure passthrough.
|
|
|
|
|
synth(value, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)
|
|
|
|
|
synth(value, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)
|
|
|
|
|
}
|
|
|
|
|
Term::ReuseAs { source, body } => {
|
|
|
|
|
// Iter 18d.1: `(reuse-as SRC NEW-CTOR)` requires `body` to
|
|
|
|
@@ -3616,7 +3774,7 @@ pub(crate) fn synth(
|
|
|
|
|
// shape-compatibility check (18d.2 will add a
|
|
|
|
|
// `reuse-as-shape-mismatch` diagnostic when codegen has
|
|
|
|
|
// the actual size info).
|
|
|
|
|
let _ = synth(source, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
let _ = synth(source, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)?;
|
|
|
|
|
match body.as_ref() {
|
|
|
|
|
Term::Ctor { .. } | Term::Lam { .. } => {}
|
|
|
|
|
other => {
|
|
|
|
@@ -3646,7 +3804,7 @@ pub(crate) fn synth(
|
|
|
|
|
}
|
|
|
|
|
// Body's type is the result type of the whole reuse-as
|
|
|
|
|
// expression.
|
|
|
|
|
synth(body, env, locals, mut_scope_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)
|
|
|
|
|
synth(body, env, locals, mut_scope_stack, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings)
|
|
|
|
|
}
|
|
|
|
|
// Iter mut.2: a mut block opens a fresh lexical scope for its
|
|
|
|
|
// mut-vars. Each var's `init` is synthed in the outer scope
|
|
|
|
@@ -3672,7 +3830,7 @@ pub(crate) fn synth(
|
|
|
|
|
// the `mut_scope_stack` truthful at all times.
|
|
|
|
|
mut_scope_stack.push(frame.clone());
|
|
|
|
|
let init_ty = synth(
|
|
|
|
|
&v.init, env, locals, mut_scope_stack, effects, in_def,
|
|
|
|
|
&v.init, env, locals, mut_scope_stack, loop_stack, effects, in_def,
|
|
|
|
|
subst, counter, residuals, free_fn_calls, warnings,
|
|
|
|
|
)?;
|
|
|
|
|
mut_scope_stack.pop();
|
|
|
|
@@ -3681,7 +3839,7 @@ pub(crate) fn synth(
|
|
|
|
|
}
|
|
|
|
|
mut_scope_stack.push(frame);
|
|
|
|
|
let body_result = synth(
|
|
|
|
|
body, env, locals, mut_scope_stack, effects, in_def,
|
|
|
|
|
body, env, locals, mut_scope_stack, loop_stack, effects, in_def,
|
|
|
|
|
subst, counter, residuals, free_fn_calls, warnings,
|
|
|
|
|
);
|
|
|
|
|
mut_scope_stack.pop();
|
|
|
|
@@ -3715,7 +3873,7 @@ pub(crate) fn synth(
|
|
|
|
|
}
|
|
|
|
|
Some(declared_ty) => {
|
|
|
|
|
let value_ty = synth(
|
|
|
|
|
value, env, locals, mut_scope_stack, effects, in_def,
|
|
|
|
|
value, env, locals, mut_scope_stack, loop_stack, effects, in_def,
|
|
|
|
|
subst, counter, residuals, free_fn_calls, warnings,
|
|
|
|
|
)?;
|
|
|
|
|
let applied_declared = subst.apply(&declared_ty);
|
|
|
|
@@ -3731,15 +3889,83 @@ pub(crate) fn synth(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// loop-recur iter 1: typecheck semantics (binder typing,
|
|
|
|
|
// recur arity/type unification, verify_loop_body
|
|
|
|
|
// tail-position, the four Recur* CheckError variants) land
|
|
|
|
|
// in loop-recur iter 2. This iter stubs the dispatch so the
|
|
|
|
|
// workspace compiles and round-trips; no loop/recur fixture
|
|
|
|
|
// is typechecked in iter 1. Mirrors mut.1's synth stub.
|
|
|
|
|
Term::Loop { .. } | Term::Recur { .. } => Err(CheckError::Internal(
|
|
|
|
|
"Term::Loop/Term::Recur typecheck lands in loop-recur iter 2".into(),
|
|
|
|
|
)),
|
|
|
|
|
// loop-recur iter 2: each binder init is synthed in the
|
|
|
|
|
// outer scope plus already-declared binder names of THIS
|
|
|
|
|
// loop (declaration order; later inits + body see earlier
|
|
|
|
|
// binders via `locals`, exactly as `Term::Let` binds its
|
|
|
|
|
// name). The loop's static type is the body's type. The
|
|
|
|
|
// ordered binder types are pushed onto `loop_stack` for the
|
|
|
|
|
// body walk so an inner `Term::Recur` can position-check
|
|
|
|
|
// against them; popped on exit. Binder names are NOT in
|
|
|
|
|
// `loop_stack` (recur is positional, not by-name).
|
|
|
|
|
Term::Loop { binders, body } => {
|
|
|
|
|
let mut binder_tys: Vec<Type> = Vec::new();
|
|
|
|
|
let mut restore: Vec<(String, Option<Type>)> = Vec::new();
|
|
|
|
|
for b in binders {
|
|
|
|
|
let init_ty = synth(
|
|
|
|
|
&b.init, env, locals, mut_scope_stack, loop_stack, effects,
|
|
|
|
|
in_def, subst, counter, residuals, free_fn_calls, warnings,
|
|
|
|
|
)?;
|
|
|
|
|
unify(&b.ty, &init_ty, subst)?;
|
|
|
|
|
binder_tys.push(b.ty.clone());
|
|
|
|
|
let prev = locals.insert(b.name.clone(), b.ty.clone());
|
|
|
|
|
restore.push((b.name.clone(), prev));
|
|
|
|
|
}
|
|
|
|
|
loop_stack.push(binder_tys);
|
|
|
|
|
let body_result = synth(
|
|
|
|
|
body, env, locals, mut_scope_stack, loop_stack, effects,
|
|
|
|
|
in_def, subst, counter, residuals, free_fn_calls, warnings,
|
|
|
|
|
);
|
|
|
|
|
loop_stack.pop();
|
|
|
|
|
for (name, prev) in restore.into_iter().rev() {
|
|
|
|
|
match prev {
|
|
|
|
|
Some(p) => {
|
|
|
|
|
locals.insert(name, p);
|
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
locals.shift_remove(&name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
body_result
|
|
|
|
|
}
|
|
|
|
|
// loop-recur iter 2: recur re-enters the innermost enclosing
|
|
|
|
|
// loop, rebinding its binders positionally. RecurOutsideLoop
|
|
|
|
|
// when `loop_stack` is empty; RecurArityMismatch on count
|
|
|
|
|
// disagreement; RecurTypeMismatch via the Assign-style
|
|
|
|
|
// subst-apply-then-structural-compare (Boss call 1) so the
|
|
|
|
|
// dedicated code fires point-exactly. recur's OWN type is a
|
|
|
|
|
// fresh metavar — it transfers control and never falls
|
|
|
|
|
// through, so it unifies with whatever sibling branch the
|
|
|
|
|
// enclosing if/match requires.
|
|
|
|
|
Term::Recur { args } => {
|
|
|
|
|
let binder_tys = match loop_stack.last() {
|
|
|
|
|
None => return Err(CheckError::RecurOutsideLoop),
|
|
|
|
|
Some(tys) => tys.clone(),
|
|
|
|
|
};
|
|
|
|
|
if args.len() != binder_tys.len() {
|
|
|
|
|
return Err(CheckError::RecurArityMismatch {
|
|
|
|
|
expected: binder_tys.len(),
|
|
|
|
|
got: args.len(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
for (i, a) in args.iter().enumerate() {
|
|
|
|
|
let arg_ty = synth(
|
|
|
|
|
a, env, locals, mut_scope_stack, loop_stack, effects,
|
|
|
|
|
in_def, subst, counter, residuals, free_fn_calls, warnings,
|
|
|
|
|
)?;
|
|
|
|
|
let applied_binder = subst.apply(&binder_tys[i]);
|
|
|
|
|
let applied_arg = subst.apply(&arg_ty);
|
|
|
|
|
if applied_binder != applied_arg {
|
|
|
|
|
return Err(CheckError::RecurTypeMismatch {
|
|
|
|
|
position: i,
|
|
|
|
|
expected: ailang_core::pretty::type_to_string(&applied_binder),
|
|
|
|
|
got: ailang_core::pretty::type_to_string(&applied_arg),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(Subst::fresh(counter))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -4114,6 +4340,9 @@ mod tests {
|
|
|
|
|
let env = Env::default();
|
|
|
|
|
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
// loop-recur iter 2: loop-free unit-test call site — fresh
|
|
|
|
|
// empty loop-stack, mirroring the mut-scope above.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let mut effects: BTreeSet<String> = BTreeSet::new();
|
|
|
|
|
let mut subst = Subst::default();
|
|
|
|
|
let mut counter: u32 = 0;
|
|
|
|
@@ -4125,6 +4354,7 @@ mod tests {
|
|
|
|
|
&env,
|
|
|
|
|
&mut locals,
|
|
|
|
|
&mut mut_scope_stack,
|
|
|
|
|
&mut loop_stack,
|
|
|
|
|
&mut effects,
|
|
|
|
|
"<test>",
|
|
|
|
|
&mut subst,
|
|
|
|
@@ -4151,6 +4381,9 @@ mod tests {
|
|
|
|
|
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
locals.insert("x".into(), Type::int()); // outer let-style binding
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
// loop-recur iter 2: loop-free unit-test call site — fresh
|
|
|
|
|
// empty loop-stack, mirroring the mut-scope above.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let mut frame: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
frame.insert("x".into(), Type::float()); // inner mut-var
|
|
|
|
|
mut_scope_stack.push(frame);
|
|
|
|
@@ -4167,6 +4400,7 @@ mod tests {
|
|
|
|
|
&env,
|
|
|
|
|
&mut locals,
|
|
|
|
|
&mut mut_scope_stack,
|
|
|
|
|
&mut loop_stack,
|
|
|
|
|
&mut effects,
|
|
|
|
|
"<test>",
|
|
|
|
|
&mut subst,
|
|
|
|
@@ -4191,6 +4425,9 @@ mod tests {
|
|
|
|
|
let env = Env::default();
|
|
|
|
|
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
// loop-recur iter 2: loop-free unit-test call site — fresh
|
|
|
|
|
// empty loop-stack, mirroring the mut-scope above.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let mut outer: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
outer.insert("x".into(), Type::int());
|
|
|
|
|
mut_scope_stack.push(outer);
|
|
|
|
@@ -4210,6 +4447,7 @@ mod tests {
|
|
|
|
|
&env,
|
|
|
|
|
&mut locals,
|
|
|
|
|
&mut mut_scope_stack,
|
|
|
|
|
&mut loop_stack,
|
|
|
|
|
&mut effects,
|
|
|
|
|
"<test>",
|
|
|
|
|
&mut subst,
|
|
|
|
@@ -4304,6 +4542,9 @@ mod tests {
|
|
|
|
|
let env = Env::default();
|
|
|
|
|
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
// loop-recur iter 2: loop-free unit-test call site — fresh
|
|
|
|
|
// empty loop-stack, mirroring the mut-scope above.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let mut effects: BTreeSet<String> = BTreeSet::new();
|
|
|
|
|
let mut subst = Subst::default();
|
|
|
|
|
let mut counter: u32 = 0;
|
|
|
|
@@ -4311,7 +4552,7 @@ mod tests {
|
|
|
|
|
let mut free_fn_calls: Vec<FreeFnCall> = Vec::new();
|
|
|
|
|
let mut warnings: Vec<Diagnostic> = Vec::new();
|
|
|
|
|
let err = synth(
|
|
|
|
|
&term, &env, &mut locals, &mut mut_scope_stack, &mut effects,
|
|
|
|
|
&term, &env, &mut locals, &mut mut_scope_stack, &mut loop_stack, &mut effects,
|
|
|
|
|
"<test>", &mut subst, &mut counter, &mut residuals,
|
|
|
|
|
&mut free_fn_calls, &mut warnings,
|
|
|
|
|
)
|
|
|
|
@@ -4344,6 +4585,9 @@ mod tests {
|
|
|
|
|
let env = Env::default();
|
|
|
|
|
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
// loop-recur iter 2: loop-free unit-test call site — fresh
|
|
|
|
|
// empty loop-stack, mirroring the mut-scope above.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let mut effects: BTreeSet<String> = BTreeSet::new();
|
|
|
|
|
let mut subst = Subst::default();
|
|
|
|
|
let mut counter: u32 = 0;
|
|
|
|
@@ -4351,7 +4595,7 @@ mod tests {
|
|
|
|
|
let mut free_fn_calls: Vec<FreeFnCall> = Vec::new();
|
|
|
|
|
let mut warnings: Vec<Diagnostic> = Vec::new();
|
|
|
|
|
let err = synth(
|
|
|
|
|
&term, &env, &mut locals, &mut mut_scope_stack, &mut effects,
|
|
|
|
|
&term, &env, &mut locals, &mut mut_scope_stack, &mut loop_stack, &mut effects,
|
|
|
|
|
"<test>", &mut subst, &mut counter, &mut residuals,
|
|
|
|
|
&mut free_fn_calls, &mut warnings,
|
|
|
|
|
)
|
|
|
|
@@ -4383,6 +4627,9 @@ mod tests {
|
|
|
|
|
let env = Env::default();
|
|
|
|
|
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
// loop-recur iter 2: loop-free unit-test call site — fresh
|
|
|
|
|
// empty loop-stack, mirroring the mut-scope above.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let mut effects: BTreeSet<String> = BTreeSet::new();
|
|
|
|
|
let mut subst = Subst::default();
|
|
|
|
|
let mut counter: u32 = 0;
|
|
|
|
@@ -4390,7 +4637,7 @@ mod tests {
|
|
|
|
|
let mut free_fn_calls: Vec<FreeFnCall> = Vec::new();
|
|
|
|
|
let mut warnings: Vec<Diagnostic> = Vec::new();
|
|
|
|
|
let err = synth(
|
|
|
|
|
&term, &env, &mut locals, &mut mut_scope_stack, &mut effects,
|
|
|
|
|
&term, &env, &mut locals, &mut mut_scope_stack, &mut loop_stack, &mut effects,
|
|
|
|
|
"<test>", &mut subst, &mut counter, &mut residuals,
|
|
|
|
|
&mut free_fn_calls, &mut warnings,
|
|
|
|
|
)
|
|
|
|
@@ -4437,6 +4684,32 @@ mod tests {
|
|
|
|
|
let _ = e.ctx();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// loop-recur iter 2: the four `Recur*` `CheckError` variants carry
|
|
|
|
|
/// the exact stable kebab codes the spec acceptance mandates
|
|
|
|
|
/// (`recur-outside-loop` / `recur-arity-mismatch` /
|
|
|
|
|
/// `recur-type-mismatch` / `recur-not-in-tail-position`).
|
|
|
|
|
#[test]
|
|
|
|
|
fn recur_checkerror_codes_are_exact() {
|
|
|
|
|
assert_eq!(CheckError::RecurOutsideLoop.code(), "recur-outside-loop");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
CheckError::RecurArityMismatch { expected: 2, got: 1 }.code(),
|
|
|
|
|
"recur-arity-mismatch"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
CheckError::RecurTypeMismatch {
|
|
|
|
|
position: 0,
|
|
|
|
|
expected: "Int".into(),
|
|
|
|
|
got: "Bool".into(),
|
|
|
|
|
}
|
|
|
|
|
.code(),
|
|
|
|
|
"recur-type-mismatch"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
CheckError::RecurNotInTailPosition.code(),
|
|
|
|
|
"recur-not-in-tail-position"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Iter mut.4-tidy (Task 2): regression guard — a lambda built
|
|
|
|
|
/// without any enclosing mut-scope stack typechecks unchanged.
|
|
|
|
|
/// Property protected: the new free-var scan in `Term::Lam`'s synth
|
|
|
|
@@ -4485,6 +4758,9 @@ mod tests {
|
|
|
|
|
let env = Env::default();
|
|
|
|
|
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
// loop-recur iter 2: loop-free unit-test call site — fresh
|
|
|
|
|
// empty loop-stack, mirroring the mut-scope above.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let mut effects: BTreeSet<String> = BTreeSet::new();
|
|
|
|
|
let mut subst = Subst::default();
|
|
|
|
|
let mut counter: u32 = 0;
|
|
|
|
@@ -4492,7 +4768,7 @@ mod tests {
|
|
|
|
|
let mut free_fn_calls: Vec<FreeFnCall> = Vec::new();
|
|
|
|
|
let mut warnings: Vec<Diagnostic> = Vec::new();
|
|
|
|
|
let err = synth(
|
|
|
|
|
&term, &env, &mut locals, &mut mut_scope_stack, &mut effects,
|
|
|
|
|
&term, &env, &mut locals, &mut mut_scope_stack, &mut loop_stack, &mut effects,
|
|
|
|
|
"<test>", &mut subst, &mut counter, &mut residuals,
|
|
|
|
|
&mut free_fn_calls, &mut warnings,
|
|
|
|
|
)
|
|
|
|
@@ -7224,6 +7500,9 @@ mod tests {
|
|
|
|
|
let term = Term::Var { name: "show".into() };
|
|
|
|
|
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
|
|
|
|
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
|
|
|
|
// loop-recur iter 2: loop-free unit-test call site — fresh
|
|
|
|
|
// empty loop-stack, mirroring the mut-scope above.
|
|
|
|
|
let mut loop_stack: Vec<Vec<Type>> = Vec::new();
|
|
|
|
|
let mut effects: BTreeSet<String> = BTreeSet::new();
|
|
|
|
|
let mut subst = Subst::default();
|
|
|
|
|
let mut counter: u32 = 0;
|
|
|
|
@@ -7235,6 +7514,7 @@ mod tests {
|
|
|
|
|
&env,
|
|
|
|
|
&mut locals,
|
|
|
|
|
&mut mut_scope_stack,
|
|
|
|
|
&mut loop_stack,
|
|
|
|
|
&mut effects,
|
|
|
|
|
"test_call_site",
|
|
|
|
|
&mut subst,
|
|
|
|
|