nandi/rustnimpublic Fork
⑂ 27c690d
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 `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>
nandithebull committed 2026-09-19T22:35:09-07:00 Browse files
27c690d parent: eb9c264
modified src/lower.rs +41 -3
@@ -5365,9 +5365,25 @@ impl Lowerer {
53655365 // faithful mapping.
53665366 Expr::Unsafe(u) => match single_expr(&u.block) {
53675367 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+ }
53715387 },
53725388 Expr::Closure(c) => self.closure(c, expect),
53735389 // `.await` calls the thunk and waits. Calling it is what starts
@@ -7226,6 +7242,28 @@ impl Lowerer {
72267242 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
72277243 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
72287244 "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+ }
72297267 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
72307268 | "into_iter" => (recv.code.clone(), rt.clone()),
72317269 "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 starts5389 // `.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() {
2828 }
2929 println!("{}", (0..10).contains(&10));
3030
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+
3142 // `#[derive(PartialOrd)]` compares discriminants.
3243 println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High);
3344 }
@@ -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 }