nandi/rustnimpublic Fork
⑂ 5d49ce5
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 `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>
nandithebull committed 2026-09-20T13:51:54-07:00 Browse files
5d49ce5 parent: dc9c467
modified src/lower.rs +70 -1
@@ -5140,7 +5140,8 @@ impl Lowerer {
51405140 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
51415141 let Expr::If(i) = e else { unreachable!() };
51425142 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);
51445145 }
51455146 let c = self.expr(&i.cond)?;
51465147 self.line(&format!("if {}:", c.code));
@@ -6887,6 +6888,11 @@ impl Lowerer {
68876888 }
68886889
68896890 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+ }
68906896 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
68916897 return Err(
68926898 "an `if` used as a value must have an `else` and single-expression arms".into(),
@@ -8300,6 +8306,19 @@ impl Lowerer {
83008306 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
83018307 if let Some((def, v)) = self.resolve_variant(&p.path) {
83028308 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+ }
83038322 return Ok(Val::new(
83048323 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
83058324 Some(ty),
@@ -10834,3 +10853,53 @@ impl syn::visit_mut::VisitMut for QualRenamer<'_> {
1083410853 syn::visit_mut::visit_path_mut(self, p);
1083510854 }
1083610855 }
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+}