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>
421df4e parent: 5861a17 modified
src/lower.rs +76 -0 | @@ -8768,6 +8768,82 @@ impl Lowerer { | ||
| 8768 | 8768 | // the arguments are lowered. The closure is expanded inline, with its |
| 8769 | 8769 | // parameter aliased to the payload: that keeps the whole thing an |
| 8770 | 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 | 8847 | if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 { |
| 8772 | 8848 | if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) = |
| 8773 | 8849 | (recv.ty.clone(), &m.args[0]) |
| @@ -8768,6 +8768,82 @@ impl Lowerer { | |||
| 8768 | // the arguments are lowered. The closure is expanded inline, with its | 8768 | // the arguments are lowered. The closure is expanded inline, with its |
| 8769 | // parameter aliased to the payload: that keeps the whole thing an | 8769 | // 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 | +} | ||