nandi/rustnimpublic Fork
⑂ badeef1
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 a `match` used as a value

Nim's `case` is an expression only when every arm is one, and Rust's arms
routinely bind and destructure, so this is not a translation of `match` into
`case`. It is the statement form rustnim already has, writing into a
temporary, with the temporary as the value -- which is exactly what
`let x = match ..` was already doing.

The type comes from the expectation where there is one, and otherwise from
lowering the arms in a scratch buffer, the same way an opaque return type is
read off a body.

tests/cases/062, including a `match` nested inside a `format!` argument and
inside arithmetic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T19:31:40-07:00 Browse files
badeef1 parent: 64cb049
modified src/lower.rs +28 -0
@@ -5405,6 +5405,34 @@ impl Lowerer {
54055405 Err("block expression with statements in value position is not implemented yet".into())
54065406 }
54075407 }
5408+ // A `match` in value position. Nim's `case` is an expression only
5409+ // when every arm is one, and Rust's arms routinely bind and
5410+ // destructure -- so instead the whole thing is lowered as the
5411+ // statement it already knows how to be, writing into a temporary,
5412+ // and the temporary is the value. That is what the statement form
5413+ // does for `let x = match ..` too.
5414+ Expr::Match(_) => {
5415+ let t = match expect {
5416+ Some(t) => t.clone(),
5417+ None => {
5418+ let save = self.out.len();
5419+ let probe = self.result_ty(e);
5420+ self.out.truncate(save);
5421+ probe.ok_or_else(|| {
5422+ "cannot infer the type of a `match` used as a value; \
5423+ annotate the binding or the return type"
5424+ .to_string()
5425+ })?
5426+ }
5427+ };
5428+ let tmp = self.fresh("MatchVal");
5429+ self.line(&format!("var {}: {}", tmp, t.render()));
5430+ let outer = self.target.replace((tmp.clone(), Some(t.clone())));
5431+ let r = self.match_stmt(e);
5432+ self.target = outer;
5433+ r?;
5434+ Ok(Val::new(tmp, Some(t)))
5435+ }
54085436 other => Err(format!(
54095437 "unsupported expression in value position: {} -- `{}`",
54105438 expr_kind(other),
@@ -5405,6 +5405,34 @@ impl Lowerer {
5405 Err("block expression with statements in value position is not implemented yet".into())5405 Err("block expression with statements in value position is not implemented yet".into())
5406 }5406 }
5407 }5407 }
5408+ // A `match` in value position. Nim's `case` is an expression only
5409+ // when every arm is one, and Rust's arms routinely bind and
5410+ // destructure -- so instead the whole thing is lowered as the
5411+ // statement it already knows how to be, writing into a temporary,
5412+ // and the temporary is the value. That is what the statement form
5413+ // does for `let x = match ..` too.
5414+ Expr::Match(_) => {
5415+ let t = match expect {
5416+ Some(t) => t.clone(),
5417+ None => {
5418+ let save = self.out.len();
5419+ let probe = self.result_ty(e);
5420+ self.out.truncate(save);
5421+ probe.ok_or_else(|| {
5422+ "cannot infer the type of a `match` used as a value; \
5423+ annotate the binding or the return type"
5424+ .to_string()
5425+ })?
5426+ }
5427+ };
5428+ let tmp = self.fresh("MatchVal");
5429+ self.line(&format!("var {}: {}", tmp, t.render()));
5430+ let outer = self.target.replace((tmp.clone(), Some(t.clone())));
5431+ let r = self.match_stmt(e);
5432+ self.target = outer;
5433+ r?;
5434+ Ok(Val::new(tmp, Some(t)))
5435+ }
5408 other => Err(format!(5436 other => Err(format!(
5409 "unsupported expression in value position: {} -- `{}`",5437 "unsupported expression in value position: {} -- `{}`",
5410 expr_kind(other),5438 expr_kind(other),
added tests/cases/062-match-as-value.rs +41 -0
new file mode 100644
@@ -0,0 +1,41 @@
1+// A `match` used as a value. Nim's `case` is an expression only when every arm
2+// is one, and Rust's arms routinely bind and destructure — so it is lowered as
3+// the statement it already knows how to be, writing into a temporary.
4+enum Shape {
5+ Circle(u32),
6+ Rect(u32, u32),
7+ Point,
8+}
9+
10+fn area(s: &Shape) -> u32 {
11+ match s {
12+ Shape::Circle(r) => r * r * 3,
13+ Shape::Rect(w, h) => w * h,
14+ Shape::Point => 0,
15+ }
16+}
17+
18+fn describe(s: &Shape) -> String {
19+ format!(
20+ "{}/{}",
21+ match s {
22+ Shape::Circle(_) => "circle",
23+ Shape::Rect(_, _) => "rect",
24+ Shape::Point => "point",
25+ },
26+ area(s)
27+ )
28+}
29+
30+fn main() {
31+ let shapes = vec![Shape::Circle(2u32), Shape::Rect(3u32, 4u32), Shape::Point];
32+ for s in &shapes {
33+ println!("{}", describe(s));
34+ }
35+ // Nested in an arithmetic expression.
36+ let n: u32 = 1 + match &shapes[0] {
37+ Shape::Circle(r) => *r,
38+ _ => 100,
39+ };
40+ println!("{}", n);
41+}
new file mode 100644
@@ -0,0 +1,41 @@
1+// A `match` used as a value. Nim's `case` is an expression only when every arm
2+// is one, and Rust's arms routinely bind and destructure — so it is lowered as
3+// the statement it already knows how to be, writing into a temporary.
4+enum Shape {
5+ Circle(u32),
6+ Rect(u32, u32),
7+ Point,
8+}
9+
10+fn area(s: &Shape) -> u32 {
11+ match s {
12+ Shape::Circle(r) => r * r * 3,
13+ Shape::Rect(w, h) => w * h,
14+ Shape::Point => 0,
15+ }
16+}
17+
18+fn describe(s: &Shape) -> String {
19+ format!(
20+ "{}/{}",
21+ match s {
22+ Shape::Circle(_) => "circle",
23+ Shape::Rect(_, _) => "rect",
24+ Shape::Point => "point",
25+ },
26+ area(s)
27+ )
28+}
29+
30+fn main() {
31+ let shapes = vec![Shape::Circle(2u32), Shape::Rect(3u32, 4u32), Shape::Point];
32+ for s in &shapes {
33+ println!("{}", describe(s));
34+ }
35+ // Nested in an arithmetic expression.
36+ let n: u32 = 1 + match &shapes[0] {
37+ Shape::Circle(r) => *r,
38+ _ => 100,
39+ };
40+ println!("{}", n);
41+}