nandi/rustnimpublic Fork
⑂ 1250a56
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 the terminal iterator operations, and reference patterns

`find`, `any`, `all` and `position` consume the iterator rather than adapting
it, so unlike `map` and `filter` they cannot join the index chain -- they
become the loop. Each stops at the first element that settles the answer,
which is what Rust's do, so the `break` is part of the meaning rather than an
optimisation.

`|&&prime|` in a closure parameter is the dereference Rust needs and Nim does
not: a reference is the value here, so the `&`s are transparent. That was
already true of `&T` everywhere else in the lowering; parameter patterns were
the one place it had not been said.

tests/cases/068.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-20T10:01:20-07:00 Browse files
1250a56 parent: c51a504
modified src/lower.rs +111 -2
@@ -139,6 +139,25 @@ impl Iter {
139139 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
140140 }
141141 }
142+
143+ /// The element type, where the chain knows it.
144+ fn elem(&self) -> Option<Nim> {
145+ match self {
146+ Iter::Elems { elem, .. } => elem.clone(),
147+ Iter::Enumerate(i) => i.elem(),
148+ Iter::Closure { elem, .. } => elem.clone(),
149+ _ => None,
150+ }
151+ }
152+
153+ /// The element at an index, as a Nim lvalue.
154+ fn at(&self, i: &str) -> String {
155+ match self {
156+ Iter::Elems { code, off, .. } => format!("{}[int({}) + {}]", code, off, i),
157+ Iter::Enumerate(inner) => inner.at(i),
158+ _ => i.to_string(),
159+ }
160+ }
142161 }
143162
144163 /// How a `for`-loop pattern name refers back into the container it came from.
@@ -4491,6 +4510,79 @@ impl Lowerer {
44914510 Ok(())
44924511 }
44934512
4513+ /// `it.find(p)` / `.any(p)` / `.all(p)` / `.position(p)`.
4514+ ///
4515+ /// These consume the iterator rather than adapting it, so unlike `map` and
4516+ /// `filter` they cannot become part of the index chain -- they become the
4517+ /// loop. Each stops at the first element that settles the answer, which is
4518+ /// what Rust's do, so a `break` is part of the meaning and not an
4519+ /// optimisation.
4520+ fn iter_terminal(
4521+ &mut self,
4522+ m: &syn::ExprMethodCall,
4523+ name: &str,
4524+ ) -> Result<Option<Val>, String> {
4525+ if m.args.len() != 1 {
4526+ return Ok(None);
4527+ }
4528+ let Expr::Closure(c) = &m.args[0] else { return Ok(None) };
4529+ if c.inputs.len() != 1 {
4530+ return Ok(None);
4531+ }
4532+ let Ok(it) = self.resolve_iter(&m.receiver) else { return Ok(None) };
4533+ let elem = match it.elem() {
4534+ Some(t) => t,
4535+ None => return Ok(None),
4536+ };
4537+
4538+ let idx = self.fresh("Idx");
4539+ let out = self.fresh(match name {
4540+ "find" => "Find",
4541+ "position" => "Pos",
4542+ "any" => "Any",
4543+ _ => "All",
4544+ });
4545+ let (decl, ty) = match name {
4546+ "find" => (
4547+ format!("var {out} = rsNone[{}]()", elem.render()),
4548+ Nim::Named("Option".into(), vec![elem.clone()]),
4549+ ),
4550+ "position" => (
4551+ format!("var {out} = rsNone[uint]()"),
4552+ Nim::Named("Option".into(), vec![Nim::Prim("uint".into())]),
4553+ ),
4554+ "any" => (format!("var {out} = false"), Nim::Prim("bool".into())),
4555+ _ => (format!("var {out} = true"), Nim::Prim("bool".into())),
4556+ };
4557+ self.line(&decl);
4558+ self.line(&format!("for {} in 0 ..< int({}):", idx, it.len()));
4559+ self.indent += 1;
4560+ self.push_scope();
4561+ self.bind_pattern(&c.inputs[0], &it, &idx)?;
4562+ let cond = self.expr(&c.body)?;
4563+ let test = if name == "all" {
4564+ format!("not ({})", cond.code)
4565+ } else {
4566+ cond.code.clone()
4567+ };
4568+ self.line(&format!("if {}:", test));
4569+ self.indent += 1;
4570+ match name {
4571+ "find" => {
4572+ let v = it.at(&idx);
4573+ self.line(&format!("{out} = rsSome[{}]({})", elem.render(), v));
4574+ }
4575+ "position" => self.line(&format!("{out} = rsSome[uint](uint({idx}))")),
4576+ "any" => self.line(&format!("{out} = true")),
4577+ _ => self.line(&format!("{out} = false")),
4578+ }
4579+ self.line("break");
4580+ self.indent -= 1;
4581+ self.pop_scope();
4582+ self.indent -= 1;
4583+ Ok(Some(Val::new(out, Some(ty))))
4584+ }
4585+
44944586 /// Resolve a chain of iterator adaptors into a single `Iter`.
44954587 ///
44964588 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
@@ -6490,9 +6582,18 @@ impl Lowerer {
64906582 let mut parts = Vec::new();
64916583 let mut ptys = Vec::new();
64926584 for (i, p) in c.inputs.iter().enumerate() {
6493- let (name, ann) = match p {
6585+ // `&x` and `&&x` in a parameter pattern are the dereference Rust
6586+ // needs and Nim does not: a reference is the value here, so the
6587+ // `&`s are transparent.
6588+ fn strip_refs(p: &Pat) -> &Pat {
6589+ match p {
6590+ Pat::Reference(r) => strip_refs(&r.pat),
6591+ other => other,
6592+ }
6593+ }
6594+ let (name, ann) = match strip_refs(p) {
64946595 Pat::Ident(id) => (id.ident.to_string(), None),
6495- Pat::Type(t) => match &*t.pat {
6596+ Pat::Type(t) => match strip_refs(&t.pat) {
64966597 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
64976598 _ => return Err("only plain identifier closure parameters are supported".into()),
64986599 },
@@ -7369,6 +7470,14 @@ impl Lowerer {
73697470 }
73707471
73717472 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
7473+ // Terminal iterator operations: they consume the chain rather than
7474+ // adapting it, so they become the loop instead of joining it.
7475+ if matches!(m.method.to_string().as_str(), "find" | "any" | "all" | "position") {
7476+ if let Some(v) = self.iter_terminal(m, &m.method.to_string())? {
7477+ return Ok(v);
7478+ }
7479+ }
7480+
73727481 // `(a..b).contains(&x)` -- a range used as a value, which is the only
73737482 // way rustnim meets one outside a `for`. Nim's `x in a..b` is the same
73747483 // test, and does not need the range to exist as an object.
@@ -139,6 +139,25 @@ impl Iter {
139 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),139 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
140 }140 }
141 }141 }
142+
143+ /// The element type, where the chain knows it.
144+ fn elem(&self) -> Option<Nim> {
145+ match self {
146+ Iter::Elems { elem, .. } => elem.clone(),
147+ Iter::Enumerate(i) => i.elem(),
148+ Iter::Closure { elem, .. } => elem.clone(),
149+ _ => None,
150+ }
151+ }
152+
153+ /// The element at an index, as a Nim lvalue.
154+ fn at(&self, i: &str) -> String {
155+ match self {
156+ Iter::Elems { code, off, .. } => format!("{}[int({}) + {}]", code, off, i),
157+ Iter::Enumerate(inner) => inner.at(i),
158+ _ => i.to_string(),
159+ }
160+ }
142 }161 }
143 162
144 /// How a `for`-loop pattern name refers back into the container it came from.163 /// How a `for`-loop pattern name refers back into the container it came from.
@@ -4491,6 +4510,79 @@ impl Lowerer {
4491 Ok(())4510 Ok(())
4492 }4511 }
4493 4512
4513+ /// `it.find(p)` / `.any(p)` / `.all(p)` / `.position(p)`.
4514+ ///
4515+ /// These consume the iterator rather than adapting it, so unlike `map` and
4516+ /// `filter` they cannot become part of the index chain -- they become the
4517+ /// loop. Each stops at the first element that settles the answer, which is
4518+ /// what Rust's do, so a `break` is part of the meaning and not an
4519+ /// optimisation.
4520+ fn iter_terminal(
4521+ &mut self,
4522+ m: &syn::ExprMethodCall,
4523+ name: &str,
4524+ ) -> Result<Option<Val>, String> {
4525+ if m.args.len() != 1 {
4526+ return Ok(None);
4527+ }
4528+ let Expr::Closure(c) = &m.args[0] else { return Ok(None) };
4529+ if c.inputs.len() != 1 {
4530+ return Ok(None);
4531+ }
4532+ let Ok(it) = self.resolve_iter(&m.receiver) else { return Ok(None) };
4533+ let elem = match it.elem() {
4534+ Some(t) => t,
4535+ None => return Ok(None),
4536+ };
4537+
4538+ let idx = self.fresh("Idx");
4539+ let out = self.fresh(match name {
4540+ "find" => "Find",
4541+ "position" => "Pos",
4542+ "any" => "Any",
4543+ _ => "All",
4544+ });
4545+ let (decl, ty) = match name {
4546+ "find" => (
4547+ format!("var {out} = rsNone[{}]()", elem.render()),
4548+ Nim::Named("Option".into(), vec![elem.clone()]),
4549+ ),
4550+ "position" => (
4551+ format!("var {out} = rsNone[uint]()"),
4552+ Nim::Named("Option".into(), vec![Nim::Prim("uint".into())]),
4553+ ),
4554+ "any" => (format!("var {out} = false"), Nim::Prim("bool".into())),
4555+ _ => (format!("var {out} = true"), Nim::Prim("bool".into())),
4556+ };
4557+ self.line(&decl);
4558+ self.line(&format!("for {} in 0 ..< int({}):", idx, it.len()));
4559+ self.indent += 1;
4560+ self.push_scope();
4561+ self.bind_pattern(&c.inputs[0], &it, &idx)?;
4562+ let cond = self.expr(&c.body)?;
4563+ let test = if name == "all" {
4564+ format!("not ({})", cond.code)
4565+ } else {
4566+ cond.code.clone()
4567+ };
4568+ self.line(&format!("if {}:", test));
4569+ self.indent += 1;
4570+ match name {
4571+ "find" => {
4572+ let v = it.at(&idx);
4573+ self.line(&format!("{out} = rsSome[{}]({})", elem.render(), v));
4574+ }
4575+ "position" => self.line(&format!("{out} = rsSome[uint](uint({idx}))")),
4576+ "any" => self.line(&format!("{out} = true")),
4577+ _ => self.line(&format!("{out} = false")),
4578+ }
4579+ self.line("break");
4580+ self.indent -= 1;
4581+ self.pop_scope();
4582+ self.indent -= 1;
4583+ Ok(Some(Val::new(out, Some(ty))))
4584+ }
4585+
4494 /// Resolve a chain of iterator adaptors into a single `Iter`.4586 /// Resolve a chain of iterator adaptors into a single `Iter`.
4495 ///4587 ///
4496 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,4588 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
@@ -6490,9 +6582,18 @@ impl Lowerer {
6490 let mut parts = Vec::new();6582 let mut parts = Vec::new();
6491 let mut ptys = Vec::new();6583 let mut ptys = Vec::new();
6492 for (i, p) in c.inputs.iter().enumerate() {6584 for (i, p) in c.inputs.iter().enumerate() {
6493- let (name, ann) = match p {6585+ // `&x` and `&&x` in a parameter pattern are the dereference Rust
6586+ // needs and Nim does not: a reference is the value here, so the
6587+ // `&`s are transparent.
6588+ fn strip_refs(p: &Pat) -> &Pat {
6589+ match p {
6590+ Pat::Reference(r) => strip_refs(&r.pat),
6591+ other => other,
6592+ }
6593+ }
6594+ let (name, ann) = match strip_refs(p) {
6494 Pat::Ident(id) => (id.ident.to_string(), None),6595 Pat::Ident(id) => (id.ident.to_string(), None),
6495- Pat::Type(t) => match &*t.pat {6596+ Pat::Type(t) => match strip_refs(&t.pat) {
6496 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),6597 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
6497 _ => return Err("only plain identifier closure parameters are supported".into()),6598 _ => return Err("only plain identifier closure parameters are supported".into()),
6498 },6599 },
@@ -7369,6 +7470,14 @@ impl Lowerer {
7369 }7470 }
7370 7471
7371 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {7472 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
7473+ // Terminal iterator operations: they consume the chain rather than
7474+ // adapting it, so they become the loop instead of joining it.
7475+ if matches!(m.method.to_string().as_str(), "find" | "any" | "all" | "position") {
7476+ if let Some(v) = self.iter_terminal(m, &m.method.to_string())? {
7477+ return Ok(v);
7478+ }
7479+ }
7480+
7372 // `(a..b).contains(&x)` -- a range used as a value, which is the only7481 // `(a..b).contains(&x)` -- a range used as a value, which is the only
7373 // way rustnim meets one outside a `for`. Nim's `x in a..b` is the same7482 // way rustnim meets one outside a `for`. Nim's `x in a..b` is the same
7374 // test, and does not need the range to exist as an object.7483 // test, and does not need the range to exist as an object.
added tests/cases/068-iter-terminal.rs +18 -0
new file mode 100644
@@ -0,0 +1,18 @@
1+// `find`, `any`, `all` and `position` consume the iterator rather than
2+// adapting it, so each becomes the loop. Every one stops at the first element
3+// that settles the answer, which is what Rust's do.
4+const PRIMES: [u32; 6] = [3, 5, 7, 11, 13, 17];
5+
6+fn main() {
7+ let v: Vec<u32> = vec![4, 8, 15, 16, 23, 42];
8+
9+ println!("{:?}", v.iter().find(|&&x| x > 14));
10+ println!("{:?}", v.iter().find(|&&x| x > 100));
11+ println!("{:?}", v.iter().position(|&x| x == 16));
12+ println!("{:?}", v.iter().position(|&x| x == 99));
13+ println!("{} {}", v.iter().any(|&x| x % 2 == 1), v.iter().any(|&x| x > 100));
14+ println!("{} {}", v.iter().all(|&x| x < 100), v.iter().all(|&x| x % 2 == 0));
15+
16+ println!("{:?}", PRIMES.iter().find(|&&p| p > 6));
17+ println!("{}", PRIMES.iter().all(|&p| p % 2 == 1));
18+}
new file mode 100644
@@ -0,0 +1,18 @@
1+// `find`, `any`, `all` and `position` consume the iterator rather than
2+// adapting it, so each becomes the loop. Every one stops at the first element
3+// that settles the answer, which is what Rust's do.
4+const PRIMES: [u32; 6] = [3, 5, 7, 11, 13, 17];
5+
6+fn main() {
7+ let v: Vec<u32> = vec![4, 8, 15, 16, 23, 42];
8+
9+ println!("{:?}", v.iter().find(|&&x| x > 14));
10+ println!("{:?}", v.iter().find(|&&x| x > 100));
11+ println!("{:?}", v.iter().position(|&x| x == 16));
12+ println!("{:?}", v.iter().position(|&x| x == 99));
13+ println!("{} {}", v.iter().any(|&x| x % 2 == 1), v.iter().any(|&x| x > 100));
14+ println!("{} {}", v.iter().all(|&x| x < 100), v.iter().all(|&x| x % 2 == 0));
15+
16+ println!("{:?}", PRIMES.iter().find(|&&p| p > 6));
17+ println!("{}", PRIMES.iter().all(|&p| p % 2 == 1));
18+}