Fold a conditional array length
`[T; if Self::COMPRESS { 0 } else { Self::NUM_POW10S * 2 }]` -- Nim's `if` is
an expression as Rust's is, and const-folds the same way when the condition
is a constant.
Both branches are converted to `int`: Rust lengths are `usize`, and a bare
literal in one arm against a `usize` const in the other is a type mismatch in
Nim. The conversion const-folds away.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>ab0405b parent: 51929ac modified
src/ty.rs +30 -0 | @@ -677,6 +677,36 @@ pub fn const_expr(e: &syn::Expr) -> Result<String, String> { | ||
| 677 | 677 | // spelling that covers all three -- identity on an integer, 0/1 on a |
| 678 | 678 | // bool, the ordinal on an enum -- and is exactly what Rust's `as` does |
| 679 | 679 | // here. |
| 680 | + // A conditional length. Nim's `if` is an expression as Rust's is, and | |
| 681 | + // const-folds the same way when the condition is a constant. | |
| 682 | + Expr::If(i) => { | |
| 683 | + let cond = const_expr(&i.cond)?; | |
| 684 | + let then = i | |
| 685 | + .then_branch | |
| 686 | + .stmts | |
| 687 | + .last() | |
| 688 | + .and_then(|s| match s { | |
| 689 | + syn::Stmt::Expr(e, None) => Some(const_expr(e)), | |
| 690 | + _ => None, | |
| 691 | + }) | |
| 692 | + .ok_or_else(|| "empty `if` branch in an array length".to_string())??; | |
| 693 | + let (_, els) = i | |
| 694 | + .else_branch | |
| 695 | + .as_ref() | |
| 696 | + .ok_or_else(|| "an `if` array length needs an `else`".to_string())?; | |
| 697 | + // Both branches are made `int`: Rust lengths are `usize`, and a | |
| 698 | + // bare literal in one arm against a `usize` const in the other is | |
| 699 | + // a type mismatch in Nim. The conversion const-folds. | |
| 700 | + Ok(format!( | |
| 701 | + "(if {}: int({}) else: int({}))", | |
| 702 | + cond, | |
| 703 | + then, | |
| 704 | + const_expr(els)? | |
| 705 | + )) | |
| 706 | + } | |
| 707 | + Expr::Unary(u) if matches!(u.op, syn::UnOp::Not(_)) => { | |
| 708 | + Ok(format!("(not {})", const_expr(&u.expr)?)) | |
| 709 | + } | |
| 680 | 710 | Expr::Cast(c) => Ok(format!("ord({})", const_expr(&c.expr)?)), |
| 681 | 711 | Expr::Lit(syn::ExprLit { lit: syn::Lit::Bool(b), .. }) => Ok(b.value().to_string()), |
| 682 | 712 | other => Err(format!( |
| @@ -677,6 +677,36 @@ pub fn const_expr(e: &syn::Expr) -> Result<String, String> { | |||
| 677 | // spelling that covers all three -- identity on an integer, 0/1 on a | 677 | // spelling that covers all three -- identity on an integer, 0/1 on a |
| 678 | // bool, the ordinal on an enum -- and is exactly what Rust's `as` does | 678 | // bool, the ordinal on an enum -- and is exactly what Rust's `as` does |
| 679 | // here. | 679 | // here. |
| 680 | + // A conditional length. Nim's `if` is an expression as Rust's is, and | ||
| 681 | + // const-folds the same way when the condition is a constant. | ||
| 682 | + Expr::If(i) => { | ||
| 683 | + let cond = const_expr(&i.cond)?; | ||
| 684 | + let then = i | ||
| 685 | + .then_branch | ||
| 686 | + .stmts | ||
| 687 | + .last() | ||
| 688 | + .and_then(|s| match s { | ||
| 689 | + syn::Stmt::Expr(e, None) => Some(const_expr(e)), | ||
| 690 | + _ => None, | ||
| 691 | + }) | ||
| 692 | + .ok_or_else(|| "empty `if` branch in an array length".to_string())??; | ||
| 693 | + let (_, els) = i | ||
| 694 | + .else_branch | ||
| 695 | + .as_ref() | ||
| 696 | + .ok_or_else(|| "an `if` array length needs an `else`".to_string())?; | ||
| 697 | + // Both branches are made `int`: Rust lengths are `usize`, and a | ||
| 698 | + // bare literal in one arm against a `usize` const in the other is | ||
| 699 | + // a type mismatch in Nim. The conversion const-folds. | ||
| 700 | + Ok(format!( | ||
| 701 | + "(if {}: int({}) else: int({}))", | ||
| 702 | + cond, | ||
| 703 | + then, | ||
| 704 | + const_expr(els)? | ||
| 705 | + )) | ||
| 706 | + } | ||
| 707 | + Expr::Unary(u) if matches!(u.op, syn::UnOp::Not(_)) => { | ||
| 708 | + Ok(format!("(not {})", const_expr(&u.expr)?)) | ||
| 709 | + } | ||
| 680 | Expr::Cast(c) => Ok(format!("ord({})", const_expr(&c.expr)?)), | 710 | Expr::Cast(c) => Ok(format!("ord({})", const_expr(&c.expr)?)), |
| 681 | Expr::Lit(syn::ExprLit { lit: syn::Lit::Bool(b), .. }) => Ok(b.value().to_string()), | 711 | Expr::Lit(syn::ExprLit { lit: syn::Lit::Bool(b), .. }) => Ok(b.value().to_string()), |
| 682 | other => Err(format!( | 712 | other => Err(format!( |
modified
tests/cases/052-const-array-len.rs +9 -0 | @@ -16,6 +16,12 @@ struct Oid; | ||
| 16 | 16 | |
| 17 | 17 | impl Oid { |
| 18 | 18 | const MAX_SIZE: usize = 5; |
| 19 | + const COMPRESS: bool = false; | |
| 20 | + | |
| 21 | + // A conditional length. Nim's `if` is an expression too. | |
| 22 | + fn table() -> [u8; if Self::COMPRESS { 1 } else { Self::MAX_SIZE * 2 }] { | |
| 23 | + [1u8; if Self::COMPRESS { 1 } else { Self::MAX_SIZE * 2 }] | |
| 24 | + } | |
| 19 | 25 | |
| 20 | 26 | // `Self::` in a length, resolving against the impl being walked. |
| 21 | 27 | fn buffer() -> [u8; Self::MAX_SIZE] { |
| @@ -56,6 +62,9 @@ fn main() { | ||
| 56 | 62 | |
| 57 | 63 | // An associated const as a length, and in the braces rustc's expanded |
| 58 | 64 | // output writes it with. |
| 65 | + let t2 = Oid::table(); | |
| 66 | + println!("{} {}", t2.len(), sum(&t2)); | |
| 67 | + | |
| 59 | 68 | let b2 = Oid::buffer(); |
| 60 | 69 | println!("{} {}", b2.len(), sum(&b2)); |
| 61 | 70 | |
| @@ -16,6 +16,12 @@ struct Oid; | |||
| 16 | 16 | ||
| 17 | impl Oid { | 17 | impl Oid { |
| 18 | const MAX_SIZE: usize = 5; | 18 | const MAX_SIZE: usize = 5; |
| 19 | + const COMPRESS: bool = false; | ||
| 20 | + | ||
| 21 | + // A conditional length. Nim's `if` is an expression too. | ||
| 22 | + fn table() -> [u8; if Self::COMPRESS { 1 } else { Self::MAX_SIZE * 2 }] { | ||
| 23 | + [1u8; if Self::COMPRESS { 1 } else { Self::MAX_SIZE * 2 }] | ||
| 24 | + } | ||
| 19 | 25 | ||
| 20 | // `Self::` in a length, resolving against the impl being walked. | 26 | // `Self::` in a length, resolving against the impl being walked. |
| 21 | fn buffer() -> [u8; Self::MAX_SIZE] { | 27 | fn buffer() -> [u8; Self::MAX_SIZE] { |
| @@ -56,6 +62,9 @@ fn main() { | |||
| 56 | 62 | ||
| 57 | // An associated const as a length, and in the braces rustc's expanded | 63 | // An associated const as a length, and in the braces rustc's expanded |
| 58 | // output writes it with. | 64 | // output writes it with. |
| 65 | + let t2 = Oid::table(); | ||
| 66 | + println!("{} {}", t2.len(), sum(&t2)); | ||
| 67 | + | ||
| 59 | let b2 = Oid::buffer(); | 68 | let b2 = Oid::buffer(); |
| 60 | println!("{} {}", b2.len(), sum(&b2)); | 69 | println!("{} {}", b2.len(), sum(&b2)); |
| 61 | 70 | ||