nandi/rustnimpublic Fork
⑂ 421df4e
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 `map_or` and `map_or_else`

`option-ext` compiles -- 76 lines of content, and the sixth of libcosmic's
dependencies to go the whole way.

The mapper may be a closure written at the call site, which is inlined like
`map`'s, or a value that happens to be callable. `option-ext` is the second:
`fn map_or2<F: FnOnce(T) -> U>(self, f: F, d: U)` forwards its own parameter,
and there is nothing there to inline.

`map_or_else` evaluates its default only on the `None`/`Err` side, which is
what Rust promises and what an `if` gives.

tests/cases/081.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-20T14:45:22-07:00 Browse files
421df4e parent: 5861a17
modified src/lower.rs +76 -0
@@ -8768,6 +8768,82 @@ impl Lowerer {
87688768 // the arguments are lowered. The closure is expanded inline, with its
87698769 // parameter aliased to the payload: that keeps the whole thing an
87708770 // expression and avoids handing a view to a generic proc.
8771+ // `map_or(default, f)` and `map_or_else(g, f)`: the closure's parameter
8772+ // type comes from the receiver the same way, so they are handled here
8773+ // too. The default is evaluated only on the `None`/`Err` side, which
8774+ // is what Rust's `map_or_else` promises and what an `if` gives.
8775+ if matches!(name.as_str(), "map_or" | "map_or_else") && m.args.len() == 2 {
8776+ if let Some(Nim::Named(kind, targs)) = recv.ty.clone() {
8777+ if (kind == "Option" && targs.len() == 1)
8778+ || (kind == "Result" && targs.len() == 2)
8779+ {
8780+ // The mapper may be a closure written here, or a value
8781+ // that happens to be callable -- `f` in
8782+ // `fn map_or2<F: FnOnce(T) -> U>(self, f: F, d: U)` is the
8783+ // second, and there is nothing to inline.
8784+ if !matches!(&m.args[1], Expr::Closure(_)) {
8785+ let f = self.expr(&m.args[1])?;
8786+ let inner = match &f.ty {
8787+ Some(Nim::Proc(_, r)) => (**r).clone(),
8788+ Some(Nim::Named(g, _)) if self.fn_bounds.contains_key(g) => {
8789+ match &self.fn_bounds[g] {
8790+ Nim::Proc(_, r) => (**r).clone(),
8791+ _ => return Err("`map_or` needs a callable".into()),
8792+ }
8793+ }
8794+ _ => {
8795+ return Err(
8796+ "`map_or` needs to know what its mapper returns".into()
8797+ )
8798+ }
8799+ };
8800+ let d = if name == "map_or" {
8801+ self.expr_at(&m.args[0], Some(&inner))?.code
8802+ } else {
8803+ format!("{}()", self.expr(&m.args[0])?.code)
8804+ };
8805+ let tmp = self.fresh("MapOr");
8806+ self.line(&format!("let {} = {}", tmp, recv.code));
8807+ let field = if kind == "Result" { "ok" } else { "has" };
8808+ return Ok(Val::new(
8809+ format!(
8810+ "(if {0}.{1}: {2}({0}.val) else: {3})",
8811+ tmp, field, f.code, d
8812+ ),
8813+ Some(inner),
8814+ ));
8815+ }
8816+ if let Expr::Closure(c) = &m.args[1] {
8817+ let mapped = self.map_closure("map", &recv, &kind, &targs, c)?;
8818+ let tmp = self.fresh("MapOr");
8819+ // `map_closure` already produced the `Some(f(x))`
8820+ // form; the default replaces the empty half.
8821+ let inner = match &mapped.ty {
8822+ Some(Nim::Named(_, a)) if !a.is_empty() => a[0].clone(),
8823+ _ => return Err("`map_or` needs a known payload type".into()),
8824+ };
8825+ let d = if name == "map_or" {
8826+ self.expr_at(&m.args[0], Some(&inner))?.code
8827+ } else {
8828+ match &m.args[0] {
8829+ Expr::Closure(g) if g.inputs.is_empty() => {
8830+ let v = self.expr_at(&g.body, Some(&inner))?;
8831+ v.code
8832+ }
8833+ other => format!("{}()", self.expr(other)?.code),
8834+ }
8835+ };
8836+ self.line(&format!("let {} = {}", tmp, mapped.code));
8837+ let field = if kind == "Result" { "ok" } else { "has" };
8838+ return Ok(Val::new(
8839+ format!("(if {0}.{1}: {0}.val else: {2})", tmp, field, d),
8840+ Some(inner),
8841+ ));
8842+ }
8843+ }
8844+ }
8845+ }
8846+
87718847 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
87728848 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
87738849 (recv.ty.clone(), &m.args[0])
@@ -8768,6 +8768,82 @@ impl Lowerer {
8768 // the arguments are lowered. The closure is expanded inline, with its8768 // the arguments are lowered. The closure is expanded inline, with its
8769 // parameter aliased to the payload: that keeps the whole thing an8769 // parameter aliased to the payload: that keeps the whole thing an
8770 // expression and avoids handing a view to a generic proc.8770 // expression and avoids handing a view to a generic proc.
8771+ // `map_or(default, f)` and `map_or_else(g, f)`: the closure's parameter
8772+ // type comes from the receiver the same way, so they are handled here
8773+ // too. The default is evaluated only on the `None`/`Err` side, which
8774+ // is what Rust's `map_or_else` promises and what an `if` gives.
8775+ if matches!(name.as_str(), "map_or" | "map_or_else") && m.args.len() == 2 {
8776+ if let Some(Nim::Named(kind, targs)) = recv.ty.clone() {
8777+ if (kind == "Option" && targs.len() == 1)
8778+ || (kind == "Result" && targs.len() == 2)
8779+ {
8780+ // The mapper may be a closure written here, or a value
8781+ // that happens to be callable -- `f` in
8782+ // `fn map_or2<F: FnOnce(T) -> U>(self, f: F, d: U)` is the
8783+ // second, and there is nothing to inline.
8784+ if !matches!(&m.args[1], Expr::Closure(_)) {
8785+ let f = self.expr(&m.args[1])?;
8786+ let inner = match &f.ty {
8787+ Some(Nim::Proc(_, r)) => (**r).clone(),
8788+ Some(Nim::Named(g, _)) if self.fn_bounds.contains_key(g) => {
8789+ match &self.fn_bounds[g] {
8790+ Nim::Proc(_, r) => (**r).clone(),
8791+ _ => return Err("`map_or` needs a callable".into()),
8792+ }
8793+ }
8794+ _ => {
8795+ return Err(
8796+ "`map_or` needs to know what its mapper returns".into()
8797+ )
8798+ }
8799+ };
8800+ let d = if name == "map_or" {
8801+ self.expr_at(&m.args[0], Some(&inner))?.code
8802+ } else {
8803+ format!("{}()", self.expr(&m.args[0])?.code)
8804+ };
8805+ let tmp = self.fresh("MapOr");
8806+ self.line(&format!("let {} = {}", tmp, recv.code));
8807+ let field = if kind == "Result" { "ok" } else { "has" };
8808+ return Ok(Val::new(
8809+ format!(
8810+ "(if {0}.{1}: {2}({0}.val) else: {3})",
8811+ tmp, field, f.code, d
8812+ ),
8813+ Some(inner),
8814+ ));
8815+ }
8816+ if let Expr::Closure(c) = &m.args[1] {
8817+ let mapped = self.map_closure("map", &recv, &kind, &targs, c)?;
8818+ let tmp = self.fresh("MapOr");
8819+ // `map_closure` already produced the `Some(f(x))`
8820+ // form; the default replaces the empty half.
8821+ let inner = match &mapped.ty {
8822+ Some(Nim::Named(_, a)) if !a.is_empty() => a[0].clone(),
8823+ _ => return Err("`map_or` needs a known payload type".into()),
8824+ };
8825+ let d = if name == "map_or" {
8826+ self.expr_at(&m.args[0], Some(&inner))?.code
8827+ } else {
8828+ match &m.args[0] {
8829+ Expr::Closure(g) if g.inputs.is_empty() => {
8830+ let v = self.expr_at(&g.body, Some(&inner))?;
8831+ v.code
8832+ }
8833+ other => format!("{}()", self.expr(other)?.code),
8834+ }
8835+ };
8836+ self.line(&format!("let {} = {}", tmp, mapped.code));
8837+ let field = if kind == "Result" { "ok" } else { "has" };
8838+ return Ok(Val::new(
8839+ format!("(if {0}.{1}: {0}.val else: {2})", tmp, field, d),
8840+ Some(inner),
8841+ ));
8842+ }
8843+ }
8844+ }
8845+ }
8846+
8771 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {8847 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
8772 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =8848 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
8773 (recv.ty.clone(), &m.args[0])8849 (recv.ty.clone(), &m.args[0])
added tests/cases/081-map-or.rs +25 -0
new file mode 100644
@@ -0,0 +1,25 @@
1+// `map_or(default, f)` and `map_or_else(g, f)`. The default is evaluated only
2+// on the `None` side in `map_or_else`, which is what an `if` gives.
3+fn describe(v: Option<u32>) -> String {
4+ v.map_or(String::from("none"), |x| format!("some {}", x))
5+}
6+
7+fn doubled(v: Option<u32>) -> u32 {
8+ v.map_or(0, |x| x * 2)
9+}
10+
11+fn lazy(v: Option<u32>) -> u32 {
12+ v.map_or_else(|| 99, |x| x + 1)
13+}
14+
15+fn from_result(r: Result<u32, String>) -> u32 {
16+ r.map_or(0, |x| x * 10)
17+}
18+
19+fn main() {
20+ println!("{}", describe(Some(4)));
21+ println!("{}", describe(None));
22+ println!("{} {}", doubled(Some(21)), doubled(None));
23+ println!("{} {}", lazy(Some(1)), lazy(None));
24+ println!("{} {}", from_result(Ok(5)), from_result(Err(String::from("e"))));
25+}
new file mode 100644
@@ -0,0 +1,25 @@
1+// `map_or(default, f)` and `map_or_else(g, f)`. The default is evaluated only
2+// on the `None` side in `map_or_else`, which is what an `if` gives.
3+fn describe(v: Option<u32>) -> String {
4+ v.map_or(String::from("none"), |x| format!("some {}", x))
5+}
6+
7+fn doubled(v: Option<u32>) -> u32 {
8+ v.map_or(0, |x| x * 2)
9+}
10+
11+fn lazy(v: Option<u32>) -> u32 {
12+ v.map_or_else(|| 99, |x| x + 1)
13+}
14+
15+fn from_result(r: Result<u32, String>) -> u32 {
16+ r.map_or(0, |x| x * 10)
17+}
18+
19+fn main() {
20+ println!("{}", describe(Some(4)));
21+ println!("{}", describe(None));
22+ println!("{} {}", doubled(Some(21)), doubled(None));
23+ println!("{} {}", lazy(Some(1)), lazy(None));
24+ println!("{} {}", from_result(Ok(5)), from_result(Err(String::from("e"))));
25+}