Lower `unsafe` blocks with statements, and `clear`/`pop`
`unsafe` is a permission marker, not a semantic change, and the block was already transparent when it held a single expression. With statements before the value it was refused; they are lowered where they stand now, and the trailing expression is the value. `unsafe` still introduces no scope of its own in the output. `clear` drops every element and keeps the allocation, which is what setting the length to zero does in Nim. `pop` returns an `Option` in Rust and requires a non-empty container in Nim, so the emptiness check is written out rather than left to differ. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
27c690d parent: eb9c264 modified
src/lower.rs +41 -3 | @@ -5365,9 +5365,25 @@ impl Lowerer { | ||
| 5365 | 5365 | // faithful mapping. |
| 5366 | 5366 | Expr::Unsafe(u) => match single_expr(&u.block) { |
| 5367 | 5367 | Some(e) => self.expr_at(e, expect), |
| 5368 | - None => Err("an `unsafe` block used as a value must be a single \ | |
| 5369 | - expression" | |
| 5370 | - .into()), | |
| 5368 | + // Statements before the value: lowered where they stand, and | |
| 5369 | + // the trailing expression is the value. `unsafe` introduces no | |
| 5370 | + // scope of its own in the output, which is what | |
| 5371 | + // `nested_block_flat` is for. | |
| 5372 | + None => { | |
| 5373 | + self.push_scope(); | |
| 5374 | + let tail = self.block_body_at(&u.block, expect); | |
| 5375 | + let tail = match tail { | |
| 5376 | + Ok(t) => t, | |
| 5377 | + Err(e) => { | |
| 5378 | + self.pop_scope(); | |
| 5379 | + return Err(e); | |
| 5380 | + } | |
| 5381 | + }; | |
| 5382 | + self.pop_scope(); | |
| 5383 | + tail.ok_or_else(|| { | |
| 5384 | + "an `unsafe` block used as a value has to end in one".to_string() | |
| 5385 | + }) | |
| 5386 | + } | |
| 5371 | 5387 | }, |
| 5372 | 5388 | Expr::Closure(c) => self.closure(c, expect), |
| 5373 | 5389 | // `.await` calls the thunk and waits. Calling it is what starts |
| @@ -7226,6 +7242,28 @@ impl Lowerer { | ||
| 7226 | 7242 | "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))), |
| 7227 | 7243 | "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))), |
| 7228 | 7244 | "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)), |
| 7245 | + // `clear` drops every element and keeps the allocation, which is | |
| 7246 | + // what setting the length to zero does in Nim too. | |
| 7247 | + "clear" if matches!(rt, Some(Nim::Seq(_)) | Some(Nim::Prim(_))) => { | |
| 7248 | + (format!("{}.setLen(0)", recv.code), Some(Nim::Unit)) | |
| 7249 | + } | |
| 7250 | + // `pop` returns the last element, or `None` when there is none; | |
| 7251 | + // Nim's `pop` requires the container to be non-empty, so the check | |
| 7252 | + // is written out. | |
| 7253 | + "pop" if matches!(rt, Some(Nim::Seq(_))) => { | |
| 7254 | + let elem = match &rt { | |
| 7255 | + Some(Nim::Seq(e)) => (**e).clone(), | |
| 7256 | + _ => unreachable!(), | |
| 7257 | + }; | |
| 7258 | + ( | |
| 7259 | + format!( | |
| 7260 | + "(if {0}.len > 0: rsSome[{1}]({0}.pop()) else: rsNone[{1}]())", | |
| 7261 | + recv.code, | |
| 7262 | + elem.render() | |
| 7263 | + ), | |
| 7264 | + Some(Nim::Named("Option".into(), vec![elem])), | |
| 7265 | + ) | |
| 7266 | + } | |
| 7229 | 7267 | "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter" |
| 7230 | 7268 | | "into_iter" => (recv.code.clone(), rt.clone()), |
| 7231 | 7269 | "unwrap" | "expect" => { |
| @@ -5365,9 +5365,25 @@ impl Lowerer { | |||
| 5365 | // faithful mapping. | 5365 | // faithful mapping. |
| 5366 | Expr::Unsafe(u) => match single_expr(&u.block) { | 5366 | Expr::Unsafe(u) => match single_expr(&u.block) { |
| 5367 | Some(e) => self.expr_at(e, expect), | 5367 | Some(e) => self.expr_at(e, expect), |
| 5368 | - None => Err("an `unsafe` block used as a value must be a single \ | 5368 | + // Statements before the value: lowered where they stand, and |
| 5369 | - expression" | 5369 | + // the trailing expression is the value. `unsafe` introduces no |
| 5370 | - .into()), | 5370 | + // scope of its own in the output, which is what |
| 5371 | + // `nested_block_flat` is for. | ||
| 5372 | + None => { | ||
| 5373 | + self.push_scope(); | ||
| 5374 | + let tail = self.block_body_at(&u.block, expect); | ||
| 5375 | + let tail = match tail { | ||
| 5376 | + Ok(t) => t, | ||
| 5377 | + Err(e) => { | ||
| 5378 | + self.pop_scope(); | ||
| 5379 | + return Err(e); | ||
| 5380 | + } | ||
| 5381 | + }; | ||
| 5382 | + self.pop_scope(); | ||
| 5383 | + tail.ok_or_else(|| { | ||
| 5384 | + "an `unsafe` block used as a value has to end in one".to_string() | ||
| 5385 | + }) | ||
| 5386 | + } | ||
| 5371 | }, | 5387 | }, |
| 5372 | Expr::Closure(c) => self.closure(c, expect), | 5388 | Expr::Closure(c) => self.closure(c, expect), |
| 5373 | // `.await` calls the thunk and waits. Calling it is what starts | 5389 | // `.await` calls the thunk and waits. Calling it is what starts |
| @@ -7226,6 +7242,28 @@ impl Lowerer { | |||
| 7226 | "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))), | 7242 | "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))), |
| 7227 | "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))), | 7243 | "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))), |
| 7228 | "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)), | 7244 | "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)), |
| 7245 | + // `clear` drops every element and keeps the allocation, which is | ||
| 7246 | + // what setting the length to zero does in Nim too. | ||
| 7247 | + "clear" if matches!(rt, Some(Nim::Seq(_)) | Some(Nim::Prim(_))) => { | ||
| 7248 | + (format!("{}.setLen(0)", recv.code), Some(Nim::Unit)) | ||
| 7249 | + } | ||
| 7250 | + // `pop` returns the last element, or `None` when there is none; | ||
| 7251 | + // Nim's `pop` requires the container to be non-empty, so the check | ||
| 7252 | + // is written out. | ||
| 7253 | + "pop" if matches!(rt, Some(Nim::Seq(_))) => { | ||
| 7254 | + let elem = match &rt { | ||
| 7255 | + Some(Nim::Seq(e)) => (**e).clone(), | ||
| 7256 | + _ => unreachable!(), | ||
| 7257 | + }; | ||
| 7258 | + ( | ||
| 7259 | + format!( | ||
| 7260 | + "(if {0}.len > 0: rsSome[{1}]({0}.pop()) else: rsNone[{1}]())", | ||
| 7261 | + recv.code, | ||
| 7262 | + elem.render() | ||
| 7263 | + ), | ||
| 7264 | + Some(Nim::Named("Option".into(), vec![elem])), | ||
| 7265 | + ) | ||
| 7266 | + } | ||
| 7229 | "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter" | 7267 | "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter" |
| 7230 | | "into_iter" => (recv.code.clone(), rt.clone()), | 7268 | | "into_iter" => (recv.code.clone(), rt.clone()), |
| 7231 | "unwrap" | "expect" => { | 7269 | "unwrap" | "expect" => { |
modified
tests/cases/066-std-subset.rs +11 -0 | @@ -28,6 +28,17 @@ fn main() { | ||
| 28 | 28 | } |
| 29 | 29 | println!("{}", (0..10).contains(&10)); |
| 30 | 30 | |
| 31 | + // `clear` keeps the allocation and drops the elements; `pop` yields an | |
| 32 | + // Option, which Nim's needs written out because it requires non-empty. | |
| 33 | + let mut w: Vec<u32> = vec![1, 2, 3]; | |
| 34 | + println!("{:?} {:?}", w.pop(), w.pop()); | |
| 35 | + w.clear(); | |
| 36 | + println!("{} {:?}", w.len(), w.pop()); | |
| 37 | + | |
| 38 | + let mut t = String::from("abc"); | |
| 39 | + t.clear(); | |
| 40 | + println!("[{}] {}", t, t.len()); | |
| 41 | + | |
| 31 | 42 | // `#[derive(PartialOrd)]` compares discriminants. |
| 32 | 43 | println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High); |
| 33 | 44 | } |
| @@ -28,6 +28,17 @@ fn main() { | |||
| 28 | } | 28 | } |
| 29 | println!("{}", (0..10).contains(&10)); | 29 | println!("{}", (0..10).contains(&10)); |
| 30 | 30 | ||
| 31 | + // `clear` keeps the allocation and drops the elements; `pop` yields an | ||
| 32 | + // Option, which Nim's needs written out because it requires non-empty. | ||
| 33 | + let mut w: Vec<u32> = vec![1, 2, 3]; | ||
| 34 | + println!("{:?} {:?}", w.pop(), w.pop()); | ||
| 35 | + w.clear(); | ||
| 36 | + println!("{} {:?}", w.len(), w.pop()); | ||
| 37 | + | ||
| 38 | + let mut t = String::from("abc"); | ||
| 39 | + t.clear(); | ||
| 40 | + println!("[{}] {}", t, t.len()); | ||
| 41 | + | ||
| 31 | // `#[derive(PartialOrd)]` compares discriminants. | 42 | // `#[derive(PartialOrd)]` compares discriminants. |
| 32 | println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High); | 43 | println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High); |
| 33 | } | 44 | } |