plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
a3ab768
— Peter John
2026-10-25T16:25:50+05:30
fix(monomorphize): resolve a closure's own generic from cb's declared return
- plum-checker/src/monomorphize.rs +69 -1
- plum-checker/tests/checker_tests.rs +27 -0
- plum-std/Try.plum +8 -0
plum-checker/src/monomorphize.rs
CHANGED
|
@@ -643,6 +643,14 @@ struct Monomorphizer<'a> {
|
|
|
643
643
|
/// the top of every `rewriteFnBody` call; never needs saving/restoring since
|
|
644
644
|
/// closures aren't recursed into by this pass (see `rewriteExpr`'s `Closure` arm).
|
|
645
645
|
current_return_type: Option<ast::Type>,
|
|
646
|
+
/// True while `current_return_type` holds a closure argument's declared
|
|
647
|
+
/// return (see `rewriteMethodClosureArgs`), which may still contain the
|
|
648
|
+
/// method's own unresolved generic letters.
|
|
649
|
+
return_type_is_closure_expected: bool,
|
|
650
|
+
/// The enclosing real function's return type while
|
|
651
|
+
/// `return_type_is_closure_expected` is set (fallback for letters the
|
|
652
|
+
/// closure's expected return leaves unresolved).
|
|
653
|
+
closure_outer_return_type: Option<ast::Type>,
|
|
646
654
|
/// (receiver name or `None` for a free function, function/method name) ->
|
|
647
655
|
/// (the generic enum its declared return type names, the concrete type args
|
|
648
656
|
/// it names them with) — for every function/method whose OWN declared return
|
|
@@ -772,6 +780,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
772
780
|
/// the body's construction site has just been specialized to a mangled name.
|
|
773
781
|
fn rewriteFnBody(&mut self, f: &mut ast::Fn, resolve_return: bool) -> Result<(), String> {
|
|
774
782
|
self.current_return_type = f.returns.clone();
|
|
783
|
+
self.return_type_is_closure_expected = false;
|
|
775
784
|
self.local_types_by_name.clear();
|
|
776
785
|
let mut env = self.global_env.clone();
|
|
777
786
|
if let Some(recv) = &f.type_param {
|
|
@@ -1329,6 +1338,23 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1329
1338
|
},
|
|
1330
1339
|
None => Vec::new(),
|
|
1331
1340
|
};
|
|
1341
|
+
// The closure's own declared return, with the receiver's bindings
|
|
1342
|
+
// substituted (`andThen`'s `cb: fn(T) -> Try[U, E]` on a
|
|
1343
|
+
// `Try[Int, [Bad]]` receiver -> `Try[U, [Bad]]`). A bare `Ok(n * 2)`
|
|
1344
|
+
// in the closure body can only pin `T`, never `E`; without this it
|
|
1345
|
+
// fell back to the ENCLOSING function's return type (wrong scope)
|
|
1346
|
+
// and stayed unmangled, so `resolveMethodOwnGenerics` couldn't
|
|
1347
|
+
// recover `U`. Only a non-bare-letter return is used (a bare
|
|
1348
|
+
// `fn(T) -> U` says nothing more than the outer fallback did).
|
|
1349
|
+
let closure_expected_ret: Option<ast::Type> = match &method_and_bindings {
|
|
1350
|
+
Some((method, class_bindings)) => match method.params.get(i).map(|p| &p.ty) {
|
|
1351
|
+
Some(ast::ParamType::Fn(_, Some(ret))) if !ret.generics.is_empty() => {
|
|
1352
|
+
Some(substituteType(ret, &Substitution(class_bindings.clone())))
|
|
1353
|
+
}
|
|
1354
|
+
_ => None,
|
|
1355
|
+
},
|
|
1356
|
+
None => None,
|
|
1357
|
+
};
|
|
1332
1358
|
let mut closure_env = env.clone();
|
|
1333
1359
|
for (p, ty) in cl.params.iter().zip(closure_param_tys.iter()) {
|
|
1334
1360
|
closure_env.insert(p.clone(), TypeScheme::mono(ty.clone()));
|
|
@@ -1338,7 +1364,23 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1338
1364
|
.entry(p.clone())
|
|
1339
1365
|
.or_insert_with(|| TypeScheme::mono(PlumType::TVar("_".to_string())));
|
|
1340
1366
|
}
|
|
1367
|
+
if let Some(expected) = closure_expected_ret {
|
|
1368
|
+
let saved_ret = self.current_return_type.replace(expected);
|
|
1369
|
+
let saved_flag = std::mem::replace(&mut self.return_type_is_closure_expected, true);
|
|
1370
|
+
// Nested closures keep the outermost real function's return.
|
|
1371
|
+
let saved_outer = if saved_flag {
|
|
1372
|
+
self.closure_outer_return_type.clone()
|
|
1373
|
+
} else {
|
|
1374
|
+
std::mem::replace(&mut self.closure_outer_return_type, saved_ret.clone())
|
|
1375
|
+
};
|
|
1376
|
+
let result = self.rewriteBlock(&mut cl.body, &mut closure_env);
|
|
1377
|
+
self.current_return_type = saved_ret;
|
|
1378
|
+
self.return_type_is_closure_expected = saved_flag;
|
|
1379
|
+
self.closure_outer_return_type = saved_outer;
|
|
1380
|
+
result?;
|
|
1381
|
+
} else {
|
|
1341
|
-
|
|
1382
|
+
self.rewriteBlock(&mut cl.body, &mut closure_env)?;
|
|
1383
|
+
}
|
|
1342
1384
|
}
|
|
1343
1385
|
Ok(())
|
|
1344
1386
|
}
|
|
@@ -2036,6 +2078,30 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2036
2078
|
if let Some(rt) = self.current_return_type.clone() {
|
|
2037
2079
|
if rt.name == e.name && rt.generics.len() == params.len() {
|
|
2038
2080
|
for (p, gt) in params.iter().zip(rt.generics.iter()) {
|
|
2081
|
+
// A closure's expected return can still name the
|
|
2082
|
+
// method's own not-yet-inferred letter (`U` in
|
|
2083
|
+
// `Try[U, [Bad]]`) — that's what this site is about
|
|
2084
|
+
// to determine, not a binding to copy.
|
|
2085
|
+
// Fall back to the enclosing function's return (the
|
|
2086
|
+
// pre-closure-expected behavior), so `return
|
|
2087
|
+
// x.andThen(|n| Err(Bad))` in a `-> Try[Int, [Bad]]`
|
|
2088
|
+
// function still pins `T`.
|
|
2089
|
+
if self.return_type_is_closure_expected
|
|
2090
|
+
&& gt.generics.is_empty()
|
|
2091
|
+
&& isGenericParamName(>.name)
|
|
2092
|
+
{
|
|
2093
|
+
if bindings.contains_key(p) {
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
if let Some(outer) = self.closure_outer_return_type.clone() {
|
|
2097
|
+
if outer.name == e.name && outer.generics.len() == params.len() {
|
|
2098
|
+
let og = &outer.generics[params.iter().position(|q| q == p).unwrap()];
|
|
2099
|
+
let resolved = self.resolveFieldType(og);
|
|
2100
|
+
bindings.insert(p.clone(), crate::plumTypeFromAst(&resolved));
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
continue;
|
|
2104
|
+
}
|
|
2039
2105
|
if !bindings.contains_key(p) {
|
|
2040
2106
|
// See the matching comment in
|
|
2041
2107
|
// `resolveClassInstantiation` — `gt` needs
|
|
@@ -2686,6 +2752,8 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2686
2752
|
worklist: Vec::new(),
|
|
2687
2753
|
produced: Vec::new(),
|
|
2688
2754
|
current_return_type: None,
|
|
2755
|
+
return_type_is_closure_expected: false,
|
|
2756
|
+
closure_outer_return_type: None,
|
|
2689
2757
|
fn_return_generic_enum: BTreeMap::new(),
|
|
2690
2758
|
local_types_by_name: BTreeMap::new(),
|
|
2691
2759
|
rename_counter: 0,
|
plum-checker/tests/checker_tests.rs
CHANGED
|
@@ -1680,3 +1680,30 @@ fun f() -> Try[Int, [Red]] =
|
|
|
1680
1680
|
);
|
|
1681
1681
|
assert!(bad.is_err());
|
|
1682
1682
|
}
|
|
1683
|
+
|
|
1684
|
+
#[test]
|
|
1685
|
+
fn andThenClosureReturningBareOkResolvesOwnGeneric() {
|
|
1686
|
+
// `cb: fn(T) -> Try[U, E]`: a bare `Ok(n * 2)` only pins `T`; `E` must
|
|
1687
|
+
// come from the receiver via the closure's declared return, or `U` is lost.
|
|
1688
|
+
let src = "\
|
|
1689
|
+
enum Try[T, E] =
|
|
1690
|
+
| Ok(T)
|
|
1691
|
+
| Err(E)
|
|
1692
|
+
|
|
1693
|
+
fun andThen(self, cb: fn(T) -> Try[U, E]) -> Try[U, E] =
|
|
1694
|
+
match self
|
|
1695
|
+
Ok(v) =>
|
|
1696
|
+
return cb(v)
|
|
1697
|
+
Err(e) =>
|
|
1698
|
+
return Err(e)
|
|
1699
|
+
|
|
1700
|
+
fun five() -> Try[Int, [Bad]] =
|
|
1701
|
+
return Ok(5)
|
|
1702
|
+
|
|
1703
|
+
fun main() =
|
|
1704
|
+
r := five().andThen(|n| Ok(n * 2))
|
|
1705
|
+
";
|
|
1706
|
+
let source = parseClean(src);
|
|
1707
|
+
let mono = plum_checker::monomorphize::monomorphizeSource(&source);
|
|
1708
|
+
assert!(mono.is_ok(), "expected Ok, got {:?}", mono.err());
|
|
1709
|
+
}
|
plum-std/Try.plum
CHANGED
|
@@ -104,6 +104,9 @@ fun mergeForTryTest(s: Str, i: Int) -> Try[Int, [BadNumStr, OutOfBounds]] =
|
|
|
104
104
|
x := xs.get(i)?
|
|
105
105
|
return Ok(n + x)
|
|
106
106
|
|
|
107
|
+
fun andThenBareErrForTryTest() -> Try[Int, [Bad]] =
|
|
108
|
+
return okIntForTryTest().andThen(|n| Err(Bad))
|
|
109
|
+
|
|
107
110
|
fun closureMapForTryTest(s: Str) -> Try[Int, [InvalidFirst]] =
|
|
108
111
|
n := parseInt(s) ? |e| InvalidFirst
|
|
109
112
|
return Ok(n)
|
|
@@ -121,6 +124,11 @@ test "andThen chains fallible steps"
|
|
|
121
124
|
assert a.unwrap() == 1
|
|
122
125
|
assert errIntForTryTest().andThen(|n| fifthForTryTest(n)).isErr()
|
|
123
126
|
|
|
127
|
+
test "andThen with closure returning bare Ok"
|
|
128
|
+
assert okIntForTryTest().andThen(|n| Ok(n * 2)).unwrap() == 10
|
|
129
|
+
assert errIntForTryTest().andThen(|n| Ok(n * 2)).isErr()
|
|
130
|
+
assert andThenBareErrForTryTest().isErr()
|
|
131
|
+
|
|
124
132
|
test "unwrapOr and unwrapOrElse fall back on Err"
|
|
125
133
|
assert errIntForTryTest().unwrapOr(9) == 9
|
|
126
134
|
assert okIntForTryTest().unwrapOr(9) == 5
|