plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
ef6dc10
— Peter John
2026-11-01T20:34:39+05:30
T5 coerce test + T6 review fixes: narrowing pending scoped per && operand and per item, for-clause bind shadows
plum-checker/tests/unions_coerce_test.rs
CHANGED
|
@@ -97,7 +97,6 @@ fn member_alone_is_its_own_type() {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
#[test]
|
|
100
|
-
#[ignore = "review: unify nested arm in lib.rs:834 accepts member<:enum inside TApp/TFun args; need depth guard so wrapVariantFor applies only at top level"]
|
|
101
100
|
fn invariant_list_rejected() {
|
|
102
101
|
let (r, _) = run("fun take(xs: List[Shape]) -> Int =\n 1\n\nfun go(cs: List[Circle]) -> Int =\n take(cs)");
|
|
103
102
|
assert!(r.is_err(), "List[Circle] must not pass as List[Shape]");
|
plum-core/src/union_lower.rs
CHANGED
|
@@ -10,6 +10,7 @@ use std::collections::{HashMap, HashSet};
|
|
|
10
10
|
pub fn applyPlan(source: &mut Source, plan: &UnionPlan) {
|
|
11
11
|
let mut lw = Lowerer { plan, pending: Vec::new(), needs: Vec::new() };
|
|
12
12
|
for item in &mut source.items {
|
|
13
|
+
lw.pending.clear();
|
|
13
14
|
match item {
|
|
14
15
|
Item::Fn(f) => match &mut f.body {
|
|
15
16
|
FnBody::Expr(e) => lw.expr(e),
|
|
@@ -186,11 +187,10 @@ impl<'a> Lowerer<'a> {
|
|
|
186
187
|
}
|
|
187
188
|
Expr::Unary(u) => self.expr(&mut u.operand),
|
|
188
189
|
Expr::Bool(b) => {
|
|
189
|
-
self.expr(&mut b.left);
|
|
190
190
|
let mark = self.pending.len();
|
|
191
|
-
|
|
191
|
+
self.expr(&mut b.left);
|
|
192
192
|
// Narrowings found on the left are visible on the right of `&&`.
|
|
193
|
-
let leftPairs: Vec<(String, String)> = self.pending.
|
|
193
|
+
let leftPairs: Vec<(String, String)> = self.pending[mark..].to_vec();
|
|
194
194
|
self.expr(&mut b.right);
|
|
195
195
|
if b.op == BoolOp::And && !leftPairs.is_empty() {
|
|
196
196
|
renameExpr(&mut b.right, &leftPairs);
|
|
@@ -349,7 +349,9 @@ fn rnStmt(s: &mut Stmt, from: &str, to: &str) {
|
|
|
349
349
|
ForClause::If(e) | ForClause::Bind(_, e) => rnExpr(e, from, to),
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
-
|
|
352
|
+
let shadowed = f.binders.iter().any(|b| b == from)
|
|
353
|
+
|| f.clauses.iter().any(|c| matches!(c, ForClause::Bind(n, _) if n == from));
|
|
354
|
+
if !shadowed {
|
|
353
355
|
rnBlock(&mut f.body, from, to);
|
|
354
356
|
}
|
|
355
357
|
}
|
plum-core/tests/union_lower_test.rs
CHANGED
|
@@ -327,3 +327,34 @@ fn dispatch_substitutes_member_generics() {
|
|
|
327
327
|
assert_eq!(f.params[0].ty, ParamType::Type(int.clone()));
|
|
328
328
|
assert_eq!(f.returns, Some(int));
|
|
329
329
|
}
|
|
330
|
+
|
|
331
|
+
#[test]
|
|
332
|
+
fn narrow_does_not_leak_into_later_items() {
|
|
333
|
+
let mut s = parse(
|
|
334
|
+
"fun a(s: Shape) =\n if Circle := s\n 1\nfun b(s: Shape, t: Bool) =\n if t && s.w\n s.w\n",
|
|
335
|
+
);
|
|
336
|
+
// move the bind condition into an expression-bodied fn
|
|
337
|
+
let Item::Fn(fa) = &mut s.items[0] else { panic!() };
|
|
338
|
+
let FnBody::Block(blk) = &mut fa.body else { panic!() };
|
|
339
|
+
let Stmt::If(i) = blk.stmts.remove(0) else { panic!() };
|
|
340
|
+
fa.body = FnBody::Expr(i.condition);
|
|
341
|
+
let FnBody::Expr(Expr::Bind(bd)) = &fa.body else { panic!("bind") };
|
|
342
|
+
let mut plan = UnionPlan::default();
|
|
343
|
+
plan.narrowings.insert(addr(&bd.case), ("s".into(), "Shape__Circle".into(), "s__n1".into()));
|
|
344
|
+
applyPlan(&mut s, &plan);
|
|
345
|
+
let Stmt::If(i) = &stmts(&s, "b")[0] else { panic!() };
|
|
346
|
+
let Expr::Bool(bo) = &i.condition else { panic!() };
|
|
347
|
+
assert_eq!(fieldObj(&bo.right), "s", "pending narrowing leaked across items");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#[test]
|
|
351
|
+
fn narrow_for_clause_bind_shadows() {
|
|
352
|
+
let mut s = parse(
|
|
353
|
+
"fun f(s: Shape, xs: List[Int]) =\n match s\n Circle =>\n for s := range xs\n s.r\n",
|
|
354
|
+
);
|
|
355
|
+
let plan = narrowPlan(&s, "f", "s", "Shape__Circle", "s__n1");
|
|
356
|
+
applyPlan(&mut s, &plan);
|
|
357
|
+
let m = matchOf(&stmts(&s, "f")[0]);
|
|
358
|
+
let Stmt::ForLoop(fl) = &m.cases[0].body.stmts[0] else { panic!() };
|
|
359
|
+
assert_eq!(fieldObj(exprOf(&fl.body.stmts[0])), "s");
|
|
360
|
+
}
|