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>0822ec3 parent: b33ed0d modified
src/lower.rs +84 -14 | @@ -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 | 5919 | /// `|x| x + 1` -> a Nim anonymous proc. |
| 5908 | 5920 | /// |
| 5909 | 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 | 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 | 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 | 6047 | let body = match &*c.body { |
| 5978 | 6048 | Expr::Block(b) => single_expr(&b.block) |
| 5979 | 6049 | .ok_or("a closure body with statements is not implemented yet")?, |
| @@ -5986,18 +6056,11 @@ impl Lowerer { | ||
| 5986 | 6056 | .or_else(|| v.ty.clone()) |
| 5987 | 6057 | .ok_or("cannot infer a closure's return type; annotate it")?; |
| 5988 | 6058 | 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 | + )) | |
| 6001 | 6064 | } |
| 6002 | 6065 | |
| 6003 | 6066 | /// Lower a block's statements at the current indentation, without opening |
| @@ -6498,7 +6561,14 @@ impl Lowerer { | ||
| 6498 | 6561 | // conversion, e.g. `String::from(..)`; handled by the method path. |
| 6499 | 6562 | // Calling a proc-typed local, which is how an `impl Fn(..)` parameter |
| 6500 | 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 | 6572 | return Ok(Val::new( |
| 6503 | 6573 | format!("{}({})", ident(&name), codes.join(", ")), |
| 6504 | 6574 | 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_code | 6060 | + Ok(Val::new( |
| 5991 | - } else { | 6061 | + Self::wrap_moved(proc_code, &moved, &pad), |
| 5992 | - let copies: Vec<String> = moved | 6062 | + 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 opening | 6066 | /// 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(..)` parameter | 6562 | // 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 | +} | ||