Lower `if let` as the `match` it desugars to
`if let PAT = e { a } else { b }` is `match e { PAT => a, _ => b }`. That is
Rust's own desugaring, so it is lowered as the `match` rather than given a
second implementation that could drift from the first -- and it works in
value position for free, because the `match` already does.
Also types a tuple variant's constructor arguments from the variant's declared
fields. `Shape::Circle(3)` into a `Circle(u32)` was taking the literal as an
`i32`, which is the same width-defaulting bug as `Some(7)` in the last commit
and `vec![0; 256]` before it: a value lowered before its context is known
takes Rust's default instead of the type that is about to be asked for.
tests/cases/076.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>5d49ce5 parent: dc9c467 modified
src/lower.rs +70 -1 | @@ -5140,7 +5140,8 @@ impl Lowerer { | ||
| 5140 | 5140 | fn if_stmt(&mut self, e: &Expr) -> Result<(), String> { |
| 5141 | 5141 | let Expr::If(i) = e else { unreachable!() }; |
| 5142 | 5142 | if let Expr::Let(_) = &*i.cond { |
| 5143 | - return Err("`if let` is not implemented yet".into()); | |
| 5143 | + let m = if_let_as_match(i); | |
| 5144 | + return self.match_stmt(&m); | |
| 5144 | 5145 | } |
| 5145 | 5146 | let c = self.expr(&i.cond)?; |
| 5146 | 5147 | self.line(&format!("if {}:", c.code)); |
| @@ -6887,6 +6888,11 @@ impl Lowerer { | ||
| 6887 | 6888 | } |
| 6888 | 6889 | |
| 6889 | 6890 | fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> { |
| 6891 | + // In value position too: the desugaring is the same one. | |
| 6892 | + if matches!(&*i.cond, Expr::Let(_)) { | |
| 6893 | + let m = if_let_as_match(i); | |
| 6894 | + return self.expr_at(&m, expect); | |
| 6895 | + } | |
| 6890 | 6896 | let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else { |
| 6891 | 6897 | return Err( |
| 6892 | 6898 | "an `if` used as a value must have an `else` and single-expression arms".into(), |
| @@ -8300,6 +8306,19 @@ impl Lowerer { | ||
| 8300 | 8306 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. |
| 8301 | 8307 | if let Some((def, v)) = self.resolve_variant(&p.path) { |
| 8302 | 8308 | let (ty, _) = self.variant_type(&def, expect)?; |
| 8309 | + // Re-lowered against the variant's declared field types, so a | |
| 8310 | + // literal takes the width the enum asks for rather than Rust's | |
| 8311 | + // default for an unconstrained one. | |
| 8312 | + let fields: Vec<Nim> = def | |
| 8313 | + .get(&v) | |
| 8314 | + .map(|va| va.fields.iter().map(|(_, t)| t.clone()).collect()) | |
| 8315 | + .unwrap_or_default(); | |
| 8316 | + let mut codes = codes.clone(); | |
| 8317 | + for (i, a) in c.args.iter().enumerate() { | |
| 8318 | + if let Some(want) = fields.get(i) { | |
| 8319 | + codes[i] = self.expr_at(a, Some(want))?.code; | |
| 8320 | + } | |
| 8321 | + } | |
| 8303 | 8322 | return Ok(Val::new( |
| 8304 | 8323 | format!("{}({})", def.ctor_ident(&v), codes.join(", ")), |
| 8305 | 8324 | Some(ty), |
| @@ -10834,3 +10853,53 @@ impl syn::visit_mut::VisitMut for QualRenamer<'_> { | ||
| 10834 | 10853 | syn::visit_mut::visit_path_mut(self, p); |
| 10835 | 10854 | } |
| 10836 | 10855 | } |
| 10856 | + | |
| 10857 | +/// `if let PAT = e { a } else { b }` as `match e { PAT => a, _ => b }`. | |
| 10858 | +/// | |
| 10859 | +/// That is Rust's own desugaring, so it is lowered as the `match` rather than | |
| 10860 | +/// given a second implementation that could drift from the first. | |
| 10861 | +fn if_let_as_match(i: &syn::ExprIf) -> Expr { | |
| 10862 | + let Expr::Let(l) = &*i.cond else { unreachable!() }; | |
| 10863 | + let arms = vec![ | |
| 10864 | + syn::Arm { | |
| 10865 | + attrs: Vec::new(), | |
| 10866 | + pat: (*l.pat).clone(), | |
| 10867 | + fat_arrow_token: Default::default(), | |
| 10868 | + body: Box::new(Expr::Block(syn::ExprBlock { | |
| 10869 | + attrs: Vec::new(), | |
| 10870 | + label: None, | |
| 10871 | + block: i.then_branch.clone(), | |
| 10872 | + })), | |
| 10873 | + comma: None, | |
| 10874 | + }, | |
| 10875 | + syn::Arm { | |
| 10876 | + attrs: Vec::new(), | |
| 10877 | + pat: Pat::Wild(syn::PatWild { | |
| 10878 | + attrs: Vec::new(), | |
| 10879 | + underscore_token: Default::default(), | |
| 10880 | + }), | |
| 10881 | + fat_arrow_token: Default::default(), | |
| 10882 | + body: Box::new(match &i.else_branch { | |
| 10883 | + Some((_, e)) => (**e).clone(), | |
| 10884 | + // No `else`: the other case does nothing, which an empty | |
| 10885 | + // block says. | |
| 10886 | + None => Expr::Block(syn::ExprBlock { | |
| 10887 | + attrs: Vec::new(), | |
| 10888 | + label: None, | |
| 10889 | + block: syn::Block { | |
| 10890 | + brace_token: Default::default(), | |
| 10891 | + stmts: Vec::new(), | |
| 10892 | + }, | |
| 10893 | + }), | |
| 10894 | + }), | |
| 10895 | + comma: None, | |
| 10896 | + }, | |
| 10897 | + ]; | |
| 10898 | + Expr::Match(syn::ExprMatch { | |
| 10899 | + attrs: Vec::new(), | |
| 10900 | + match_token: Default::default(), | |
| 10901 | + expr: l.expr.clone(), | |
| 10902 | + brace_token: Default::default(), | |
| 10903 | + arms, | |
| 10904 | + }) | |
| 10905 | +} | |
| @@ -5140,7 +5140,8 @@ impl Lowerer { | |||
| 5140 | fn if_stmt(&mut self, e: &Expr) -> Result<(), String> { | 5140 | fn if_stmt(&mut self, e: &Expr) -> Result<(), String> { |
| 5141 | let Expr::If(i) = e else { unreachable!() }; | 5141 | let Expr::If(i) = e else { unreachable!() }; |
| 5142 | if let Expr::Let(_) = &*i.cond { | 5142 | if let Expr::Let(_) = &*i.cond { |
| 5143 | - return Err("`if let` is not implemented yet".into()); | 5143 | + let m = if_let_as_match(i); |
| 5144 | + return self.match_stmt(&m); | ||
| 5144 | } | 5145 | } |
| 5145 | let c = self.expr(&i.cond)?; | 5146 | let c = self.expr(&i.cond)?; |
| 5146 | self.line(&format!("if {}:", c.code)); | 5147 | self.line(&format!("if {}:", c.code)); |
| @@ -6887,6 +6888,11 @@ impl Lowerer { | |||
| 6887 | } | 6888 | } |
| 6888 | 6889 | ||
| 6889 | fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> { | 6890 | fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> { |
| 6891 | + // In value position too: the desugaring is the same one. | ||
| 6892 | + if matches!(&*i.cond, Expr::Let(_)) { | ||
| 6893 | + let m = if_let_as_match(i); | ||
| 6894 | + return self.expr_at(&m, expect); | ||
| 6895 | + } | ||
| 6890 | let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else { | 6896 | let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else { |
| 6891 | return Err( | 6897 | return Err( |
| 6892 | "an `if` used as a value must have an `else` and single-expression arms".into(), | 6898 | "an `if` used as a value must have an `else` and single-expression arms".into(), |
| @@ -8300,6 +8306,19 @@ impl Lowerer { | |||
| 8300 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. | 8306 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. |
| 8301 | if let Some((def, v)) = self.resolve_variant(&p.path) { | 8307 | if let Some((def, v)) = self.resolve_variant(&p.path) { |
| 8302 | let (ty, _) = self.variant_type(&def, expect)?; | 8308 | let (ty, _) = self.variant_type(&def, expect)?; |
| 8309 | + // Re-lowered against the variant's declared field types, so a | ||
| 8310 | + // literal takes the width the enum asks for rather than Rust's | ||
| 8311 | + // default for an unconstrained one. | ||
| 8312 | + let fields: Vec<Nim> = def | ||
| 8313 | + .get(&v) | ||
| 8314 | + .map(|va| va.fields.iter().map(|(_, t)| t.clone()).collect()) | ||
| 8315 | + .unwrap_or_default(); | ||
| 8316 | + let mut codes = codes.clone(); | ||
| 8317 | + for (i, a) in c.args.iter().enumerate() { | ||
| 8318 | + if let Some(want) = fields.get(i) { | ||
| 8319 | + codes[i] = self.expr_at(a, Some(want))?.code; | ||
| 8320 | + } | ||
| 8321 | + } | ||
| 8303 | return Ok(Val::new( | 8322 | return Ok(Val::new( |
| 8304 | format!("{}({})", def.ctor_ident(&v), codes.join(", ")), | 8323 | format!("{}({})", def.ctor_ident(&v), codes.join(", ")), |
| 8305 | Some(ty), | 8324 | Some(ty), |
| @@ -10834,3 +10853,53 @@ impl syn::visit_mut::VisitMut for QualRenamer<'_> { | |||
| 10834 | syn::visit_mut::visit_path_mut(self, p); | 10853 | syn::visit_mut::visit_path_mut(self, p); |
| 10835 | } | 10854 | } |
| 10836 | } | 10855 | } |
| 10856 | + | ||
| 10857 | +/// `if let PAT = e { a } else { b }` as `match e { PAT => a, _ => b }`. | ||
| 10858 | +/// | ||
| 10859 | +/// That is Rust's own desugaring, so it is lowered as the `match` rather than | ||
| 10860 | +/// given a second implementation that could drift from the first. | ||
| 10861 | +fn if_let_as_match(i: &syn::ExprIf) -> Expr { | ||
| 10862 | + let Expr::Let(l) = &*i.cond else { unreachable!() }; | ||
| 10863 | + let arms = vec![ | ||
| 10864 | + syn::Arm { | ||
| 10865 | + attrs: Vec::new(), | ||
| 10866 | + pat: (*l.pat).clone(), | ||
| 10867 | + fat_arrow_token: Default::default(), | ||
| 10868 | + body: Box::new(Expr::Block(syn::ExprBlock { | ||
| 10869 | + attrs: Vec::new(), | ||
| 10870 | + label: None, | ||
| 10871 | + block: i.then_branch.clone(), | ||
| 10872 | + })), | ||
| 10873 | + comma: None, | ||
| 10874 | + }, | ||
| 10875 | + syn::Arm { | ||
| 10876 | + attrs: Vec::new(), | ||
| 10877 | + pat: Pat::Wild(syn::PatWild { | ||
| 10878 | + attrs: Vec::new(), | ||
| 10879 | + underscore_token: Default::default(), | ||
| 10880 | + }), | ||
| 10881 | + fat_arrow_token: Default::default(), | ||
| 10882 | + body: Box::new(match &i.else_branch { | ||
| 10883 | + Some((_, e)) => (**e).clone(), | ||
| 10884 | + // No `else`: the other case does nothing, which an empty | ||
| 10885 | + // block says. | ||
| 10886 | + None => Expr::Block(syn::ExprBlock { | ||
| 10887 | + attrs: Vec::new(), | ||
| 10888 | + label: None, | ||
| 10889 | + block: syn::Block { | ||
| 10890 | + brace_token: Default::default(), | ||
| 10891 | + stmts: Vec::new(), | ||
| 10892 | + }, | ||
| 10893 | + }), | ||
| 10894 | + }), | ||
| 10895 | + comma: None, | ||
| 10896 | + }, | ||
| 10897 | + ]; | ||
| 10898 | + Expr::Match(syn::ExprMatch { | ||
| 10899 | + attrs: Vec::new(), | ||
| 10900 | + match_token: Default::default(), | ||
| 10901 | + expr: l.expr.clone(), | ||
| 10902 | + brace_token: Default::default(), | ||
| 10903 | + arms, | ||
| 10904 | + }) | ||
| 10905 | +} | ||
added
tests/cases/076-if-let.rs +39 -0 | new file mode 100644 | ||
| @@ -0,0 +1,39 @@ | ||
| 1 | +// `if let PAT = e { a } else { b }` is `match e { PAT => a, _ => b }` — Rust's | |
| 2 | +// own desugaring, so it is lowered as that rather than given a second | |
| 3 | +// implementation that could drift from the first. | |
| 4 | +enum Shape { | |
| 5 | + Circle(u32), | |
| 6 | + Rect(u32, u32), | |
| 7 | + Point, | |
| 8 | +} | |
| 9 | + | |
| 10 | +fn radius(s: &Shape) -> String { | |
| 11 | + if let Shape::Circle(r) = s { | |
| 12 | + format!("circle {}", r) | |
| 13 | + } else { | |
| 14 | + String::from("not a circle") | |
| 15 | + } | |
| 16 | +} | |
| 17 | + | |
| 18 | +fn main() { | |
| 19 | + for s in [Shape::Circle(3), Shape::Rect(2, 5), Shape::Point] { | |
| 20 | + println!("{}", radius(&s)); | |
| 21 | + } | |
| 22 | + | |
| 23 | + let v: Option<u32> = Some(7); | |
| 24 | + if let Some(x) = v { | |
| 25 | + println!("some {}", x); | |
| 26 | + } | |
| 27 | + | |
| 28 | + let n: Option<u32> = None; | |
| 29 | + if let Some(x) = n { | |
| 30 | + println!("some {}", x); | |
| 31 | + } else { | |
| 32 | + println!("none"); | |
| 33 | + } | |
| 34 | + | |
| 35 | + // No `else`: the other case does nothing. | |
| 36 | + if let Shape::Point = Shape::Point { | |
| 37 | + println!("point"); | |
| 38 | + } | |
| 39 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | +// `if let PAT = e { a } else { b }` is `match e { PAT => a, _ => b }` — Rust's | ||
| 2 | +// own desugaring, so it is lowered as that rather than given a second | ||
| 3 | +// implementation that could drift from the first. | ||
| 4 | +enum Shape { | ||
| 5 | + Circle(u32), | ||
| 6 | + Rect(u32, u32), | ||
| 7 | + Point, | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +fn radius(s: &Shape) -> String { | ||
| 11 | + if let Shape::Circle(r) = s { | ||
| 12 | + format!("circle {}", r) | ||
| 13 | + } else { | ||
| 14 | + String::from("not a circle") | ||
| 15 | + } | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +fn main() { | ||
| 19 | + for s in [Shape::Circle(3), Shape::Rect(2, 5), Shape::Point] { | ||
| 20 | + println!("{}", radius(&s)); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + let v: Option<u32> = Some(7); | ||
| 24 | + if let Some(x) = v { | ||
| 25 | + println!("some {}", x); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + let n: Option<u32> = None; | ||
| 29 | + if let Some(x) = n { | ||
| 30 | + println!("some {}", x); | ||
| 31 | + } else { | ||
| 32 | + println!("none"); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + // No `else`: the other case does nothing. | ||
| 36 | + if let Shape::Point = Shape::Point { | ||
| 37 | + println!("point"); | ||
| 38 | + } | ||
| 39 | +} | ||