nandi/rustnimpublic Fork
⑂ 0822ec3
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Lower closure bodies with statements

Lowered the way a proc body is, into its own buffer with `result` as the
target. Nim accepts a multi-line proc inside parentheses, so the closure is
still an expression where it stands.

The return type is read off the trailing expression after the body is lowered,
not probed beforehand -- a probe cannot work, because the tail routinely names
something the block itself binds. Failing that, the *expectation* supplies it:
`impl Fn(u32) -> String` says the return type even though the closure does not
write one, which is the usual case.

Two things that had to come with it:

  * Indentation is now relative to where the closure sits, rather than a fixed
    two spaces. Nim is lenient inside parentheses but not that lenient.
  * Calling a proc-typed local re-lowers its arguments against the proc's own
    parameter types. `k(3)` was taking the literal as `i32` where `k` wanted
    `u32`, because nothing earlier in that path knew the parameter types.

tests/cases/063.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T19:38:39-07:00 Browse files
0822ec3 parent: b33ed0d
modified src/lower.rs +84 -14
@@ -5904,6 +5904,18 @@ impl Lowerer {
59045904 ))
59055905 }
59065906
5907+ /// Put a `move` closure's capture copies around it.
5908+ fn wrap_moved(proc_code: String, moved: &[(String, Nim)], pad: &str) -> String {
5909+ if moved.is_empty() {
5910+ return proc_code;
5911+ }
5912+ let copies: Vec<String> = moved
5913+ .iter()
5914+ .map(|(n, _)| format!("{pad}var {0} = {0}", ident(n)))
5915+ .collect();
5916+ format!("(block:\n{}\n{pad}{})", copies.join("\n"), proc_code)
5917+ }
5918+
59075919 /// `|x| x + 1` -> a Nim anonymous proc.
59085920 ///
59095921 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
@@ -5971,9 +5983,67 @@ impl Lowerer {
59715983 }
59725984
59735985 let ret_ann = match &c.output {
5974- ReturnType::Default => None,
5986+ ReturnType::Default => match expect {
5987+ // `impl Fn(u32) -> String` says the return type even when the
5988+ // closure does not write one, which is the usual case.
5989+ Some(Nim::Proc(_, r)) if **r != Nim::Unit => Some((**r).clone()),
5990+ _ => None,
5991+ },
59755992 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
59765993 };
5994+ // A body with statements is lowered the way a proc body is, into its
5995+ // own buffer, with `result` as the target. Nim accepts a multi-line
5996+ // proc inside parentheses, so it can still be an expression here.
5997+ let multi = match &*c.body {
5998+ Expr::Block(b) if single_expr(&b.block).is_none() => Some(b.block.clone()),
5999+ _ => None,
6000+ };
6001+ if let Some(block) = multi {
6002+ // Lowered with no target, so the trailing expression comes back as
6003+ // a value and says what the return type is; `result = ..` is then
6004+ // written explicitly. A probe beforehand cannot work: the tail
6005+ // routinely names something the block itself binds.
6006+ let base = self.indent;
6007+ let outer_out = std::mem::take(&mut self.out);
6008+ let outer_indent = std::mem::replace(&mut self.indent, base + 2);
6009+ let outer_target = self.target.take();
6010+ let r = self.block_body_at(&block, ret_ann.as_ref());
6011+ self.target = outer_target;
6012+ let tail = match r {
6013+ Ok(t) => t,
6014+ Err(e) => {
6015+ self.out = outer_out;
6016+ self.indent = outer_indent;
6017+ self.pop_scope();
6018+ return Err(e);
6019+ }
6020+ };
6021+ let ret = ret_ann
6022+ .clone()
6023+ .or_else(|| tail.as_ref().and_then(|v| v.ty.clone()))
6024+ .unwrap_or(Nim::Unit);
6025+ if ret != Nim::Unit {
6026+ if let Some(v) = &tail {
6027+ self.line(&format!("result = {}", v.code));
6028+ }
6029+ } else {
6030+ self.emit_tail(tail);
6031+ }
6032+ let body_text = std::mem::replace(&mut self.out, outer_out);
6033+ self.indent = outer_indent;
6034+ self.pop_scope();
6035+ let pad = " ".repeat(base + 1);
6036+ let proc_code = format!(
6037+ "(proc ({}): {} =\n{})",
6038+ parts.join(", "),
6039+ ret.render(),
6040+ body_text.trim_end()
6041+ );
6042+ return Ok(Val::new(
6043+ Self::wrap_moved(proc_code, &moved, &pad),
6044+ Some(Nim::Proc(ptys, Box::new(ret))),
6045+ ));
6046+ }
59776047 let body = match &*c.body {
59786048 Expr::Block(b) => single_expr(&b.block)
59796049 .ok_or("a closure body with statements is not implemented yet")?,
@@ -5986,18 +6056,11 @@ impl Lowerer {
59866056 .or_else(|| v.ty.clone())
59876057 .ok_or("cannot infer a closure's return type; annotate it")?;
59886058 let proc_code = format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code);
5989- let code = if moved.is_empty() {
5990- proc_code
5991- } else {
5992- let copies: Vec<String> = moved
5993- .iter()
5994- .map(|(n, _)| format!("var {0} = {0}", ident(n)))
5995- .collect();
5996- format!("(block:\n{}\n {})",
5997- copies.iter().map(|c| format!(" {c}")).collect::<Vec<_>>().join("\n"),
5998- proc_code)
5999- };
6000- Ok(Val::new(code, Some(Nim::Proc(ptys, Box::new(ret)))))
6059+ let pad = " ".repeat(self.indent + 1);
6060+ Ok(Val::new(
6061+ Self::wrap_moved(proc_code, &moved, &pad),
6062+ Some(Nim::Proc(ptys, Box::new(ret))),
6063+ ))
60016064 }
60026065
60036066 /// Lower a block's statements at the current indentation, without opening
@@ -6498,7 +6561,14 @@ impl Lowerer {
64986561 // conversion, e.g. `String::from(..)`; handled by the method path.
64996562 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
65006563 // is invoked.
6501- if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
6564+ if let Some(Nim::Proc(ptys, ret)) = self.lookup(&name) {
6565+ // Re-lowered against the proc's own parameter types: a literal
6566+ // argument takes its width from them, and nothing earlier in this
6567+ // function knew them.
6568+ let mut codes = Vec::new();
6569+ for (i, a) in c.args.iter().enumerate() {
6570+ codes.push(self.expr_at(a, ptys.get(i))?.code);
6571+ }
65026572 return Ok(Val::new(
65036573 format!("{}({})", ident(&name), codes.join(", ")),
65046574 Some((*ret).clone()),
@@ -5904,6 +5904,18 @@ impl Lowerer {
5904 ))5904 ))
5905 }5905 }
5906 5906
5907+ /// Put a `move` closure's capture copies around it.
5908+ fn wrap_moved(proc_code: String, moved: &[(String, Nim)], pad: &str) -> String {
5909+ if moved.is_empty() {
5910+ return proc_code;
5911+ }
5912+ let copies: Vec<String> = moved
5913+ .iter()
5914+ .map(|(n, _)| format!("{pad}var {0} = {0}", ident(n)))
5915+ .collect();
5916+ format!("(block:\n{}\n{pad}{})", copies.join("\n"), proc_code)
5917+ }
5918+
5907 /// `|x| x + 1` -> a Nim anonymous proc.5919 /// `|x| x + 1` -> a Nim anonymous proc.
5908 ///5920 ///
5909 /// Nim's closures capture by reference, as Rust's non-`move` closures do.5921 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
@@ -5971,9 +5983,67 @@ impl Lowerer {
5971 }5983 }
5972 5984
5973 let ret_ann = match &c.output {5985 let ret_ann = match &c.output {
5974- ReturnType::Default => None,5986+ ReturnType::Default => match expect {
5987+ // `impl Fn(u32) -> String` says the return type even when the
5988+ // closure does not write one, which is the usual case.
5989+ Some(Nim::Proc(_, r)) if **r != Nim::Unit => Some((**r).clone()),
5990+ _ => None,
5991+ },
5975 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),5992 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
5976 };5993 };
5994+ // A body with statements is lowered the way a proc body is, into its
5995+ // own buffer, with `result` as the target. Nim accepts a multi-line
5996+ // proc inside parentheses, so it can still be an expression here.
5997+ let multi = match &*c.body {
5998+ Expr::Block(b) if single_expr(&b.block).is_none() => Some(b.block.clone()),
5999+ _ => None,
6000+ };
6001+ if let Some(block) = multi {
6002+ // Lowered with no target, so the trailing expression comes back as
6003+ // a value and says what the return type is; `result = ..` is then
6004+ // written explicitly. A probe beforehand cannot work: the tail
6005+ // routinely names something the block itself binds.
6006+ let base = self.indent;
6007+ let outer_out = std::mem::take(&mut self.out);
6008+ let outer_indent = std::mem::replace(&mut self.indent, base + 2);
6009+ let outer_target = self.target.take();
6010+ let r = self.block_body_at(&block, ret_ann.as_ref());
6011+ self.target = outer_target;
6012+ let tail = match r {
6013+ Ok(t) => t,
6014+ Err(e) => {
6015+ self.out = outer_out;
6016+ self.indent = outer_indent;
6017+ self.pop_scope();
6018+ return Err(e);
6019+ }
6020+ };
6021+ let ret = ret_ann
6022+ .clone()
6023+ .or_else(|| tail.as_ref().and_then(|v| v.ty.clone()))
6024+ .unwrap_or(Nim::Unit);
6025+ if ret != Nim::Unit {
6026+ if let Some(v) = &tail {
6027+ self.line(&format!("result = {}", v.code));
6028+ }
6029+ } else {
6030+ self.emit_tail(tail);
6031+ }
6032+ let body_text = std::mem::replace(&mut self.out, outer_out);
6033+ self.indent = outer_indent;
6034+ self.pop_scope();
6035+ let pad = " ".repeat(base + 1);
6036+ let proc_code = format!(
6037+ "(proc ({}): {} =\n{})",
6038+ parts.join(", "),
6039+ ret.render(),
6040+ body_text.trim_end()
6041+ );
6042+ return Ok(Val::new(
6043+ Self::wrap_moved(proc_code, &moved, &pad),
6044+ Some(Nim::Proc(ptys, Box::new(ret))),
6045+ ));
6046+ }
5977 let body = match &*c.body {6047 let body = match &*c.body {
5978 Expr::Block(b) => single_expr(&b.block)6048 Expr::Block(b) => single_expr(&b.block)
5979 .ok_or("a closure body with statements is not implemented yet")?,6049 .ok_or("a closure body with statements is not implemented yet")?,
@@ -5986,18 +6056,11 @@ impl Lowerer {
5986 .or_else(|| v.ty.clone())6056 .or_else(|| v.ty.clone())
5987 .ok_or("cannot infer a closure's return type; annotate it")?;6057 .ok_or("cannot infer a closure's return type; annotate it")?;
5988 let proc_code = format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code);6058 let proc_code = format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code);
5989- let code = if moved.is_empty() {6059+ let pad = " ".repeat(self.indent + 1);
5990- proc_code6060+ Ok(Val::new(
5991- } else {6061+ Self::wrap_moved(proc_code, &moved, &pad),
5992- let copies: Vec<String> = moved6062+ Some(Nim::Proc(ptys, Box::new(ret))),
5993- .iter()6063+ ))
5994- .map(|(n, _)| format!("var {0} = {0}", ident(n)))
5995- .collect();
5996- format!("(block:\n{}\n {})",
5997- copies.iter().map(|c| format!(" {c}")).collect::<Vec<_>>().join("\n"),
5998- proc_code)
5999- };
6000- Ok(Val::new(code, Some(Nim::Proc(ptys, Box::new(ret)))))
6001 }6064 }
6002 6065
6003 /// Lower a block's statements at the current indentation, without opening6066 /// Lower a block's statements at the current indentation, without opening
@@ -6498,7 +6561,14 @@ impl Lowerer {
6498 // conversion, e.g. `String::from(..)`; handled by the method path.6561 // conversion, e.g. `String::from(..)`; handled by the method path.
6499 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter6562 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
6500 // is invoked.6563 // is invoked.
6501- if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {6564+ if let Some(Nim::Proc(ptys, ret)) = self.lookup(&name) {
6565+ // Re-lowered against the proc's own parameter types: a literal
6566+ // argument takes its width from them, and nothing earlier in this
6567+ // function knew them.
6568+ let mut codes = Vec::new();
6569+ for (i, a) in c.args.iter().enumerate() {
6570+ codes.push(self.expr_at(a, ptys.get(i))?.code);
6571+ }
6502 return Ok(Val::new(6572 return Ok(Val::new(
6503 format!("{}({})", ident(&name), codes.join(", ")),6573 format!("{}({})", ident(&name), codes.join(", ")),
6504 Some((*ret).clone()),6574 Some((*ret).clone()),
added tests/cases/063-closure-statements.rs +47 -0
new file mode 100644
@@ -0,0 +1,47 @@
1+// A closure body with statements. Lowered the way a proc body is, with
2+// `result` as the target; Nim accepts a multi-line proc inside parentheses,
3+// so it is still an expression here.
4+fn counter(start: i32) -> impl FnMut() -> i32 {
5+ let mut n = start;
6+ move || {
7+ n += 1;
8+ let doubled = n * 2;
9+ doubled
10+ }
11+}
12+
13+fn classify(limit: u32) -> impl Fn(u32) -> String {
14+ move |x| {
15+ let over = x > limit;
16+ if over {
17+ format!("{} over {}", x, limit)
18+ } else {
19+ format!("{} under {}", x, limit)
20+ }
21+ }
22+}
23+
24+fn apply(f: impl Fn(u32) -> u32, v: u32) -> u32 {
25+ f(v)
26+}
27+
28+fn main() {
29+ let mut c = counter(10);
30+ println!("{} {} {}", c(), c(), c());
31+
32+ let k = classify(5);
33+ println!("{}", k(3));
34+ println!("{}", k(9));
35+
36+ println!(
37+ "{}",
38+ apply(
39+ |x| {
40+ let a = x * 3;
41+ let b = a + 1;
42+ b
43+ },
44+ 7
45+ )
46+ );
47+}
new file mode 100644
@@ -0,0 +1,47 @@
1+// A closure body with statements. Lowered the way a proc body is, with
2+// `result` as the target; Nim accepts a multi-line proc inside parentheses,
3+// so it is still an expression here.
4+fn counter(start: i32) -> impl FnMut() -> i32 {
5+ let mut n = start;
6+ move || {
7+ n += 1;
8+ let doubled = n * 2;
9+ doubled
10+ }
11+}
12+
13+fn classify(limit: u32) -> impl Fn(u32) -> String {
14+ move |x| {
15+ let over = x > limit;
16+ if over {
17+ format!("{} over {}", x, limit)
18+ } else {
19+ format!("{} under {}", x, limit)
20+ }
21+ }
22+}
23+
24+fn apply(f: impl Fn(u32) -> u32, v: u32) -> u32 {
25+ f(v)
26+}
27+
28+fn main() {
29+ let mut c = counter(10);
30+ println!("{} {} {}", c(), c(), c());
31+
32+ let k = classify(5);
33+ println!("{}", k(3));
34+ println!("{}", k(9));
35+
36+ println!(
37+ "{}",
38+ apply(
39+ |x| {
40+ let a = x * 3;
41+ let b = a + 1;
42+ b
43+ },
44+ 7
45+ )
46+ );
47+}