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>
1250a56 parent: c51a504 modified
src/lower.rs +111 -2 | @@ -139,6 +139,25 @@ impl Iter { | ||
| 139 | 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 | 163 | /// How a `for`-loop pattern name refers back into the container it came from. |
| @@ -4491,6 +4510,79 @@ impl Lowerer { | ||
| 4491 | 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 | 4586 | /// Resolve a chain of iterator adaptors into a single `Iter`. |
| 4495 | 4587 | /// |
| 4496 | 4588 | /// Only adaptors with an exact index-loop equivalent are accepted. `map`, |
| @@ -6490,9 +6582,18 @@ impl Lowerer { | ||
| 6490 | 6582 | let mut parts = Vec::new(); |
| 6491 | 6583 | let mut ptys = Vec::new(); |
| 6492 | 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 | 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 | 6597 | Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)), |
| 6497 | 6598 | _ => return Err("only plain identifier closure parameters are supported".into()), |
| 6498 | 6599 | }, |
| @@ -7369,6 +7470,14 @@ impl Lowerer { | ||
| 7369 | 7470 | } |
| 7370 | 7471 | |
| 7371 | 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 | 7481 | // `(a..b).contains(&x)` -- a range used as a value, which is the only |
| 7373 | 7482 | // way rustnim meets one outside a `for`. Nim's `x in a..b` is the same |
| 7374 | 7483 | // 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 only | 7481 | // `(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 same | 7482 | // 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 | +} | ||