Carry array lengths across as expressions, not numbers
`[u8; BLOCK_LEN]` is how Rust spells a buffer size, and `Nim::Array` held a `usize`, so anything but a literal was refused. Nim accepts the same const expression, so the length is now kept as a Nim expression and handed over unevaluated -- resolving it to an integer would only lose information rustnim does not have at this point anyway. `ty::const_expr` renders the sub-language the two languages agree on: integer literals, a name bound by a `const`, and `+ - * / %` (the last two being Nim's `div` and `mod`). A const fn call is reported rather than guessed at. Rust array lengths are `usize` and Nim wants an ordinal count, so a non-literal length is wrapped in `int(..)`, which const-folds away. `[v; N]` still writes its elements out when `N` is a literal; when it is not, `rsArrayFill` does it, needing only the type. Found by feeding the transpiler libcosmic together with 357 expanded dependency crates -- 3.0M lines -- which is the setup that reaches this code at all. tests/cases/052. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
026a641 parent: 32c48d0 modified
src/lower.rs +17 -5 | @@ -1256,7 +1256,7 @@ impl Lowerer { | ||
| 1256 | 1256 | } |
| 1257 | 1257 | Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))), |
| 1258 | 1258 | Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))), |
| 1259 | - Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))), | |
| 1259 | + Nim::Array(n, e) => Nim::Array(n.clone(), Box::new(Self::subst(e, m))), | |
| 1260 | 1260 | Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))), |
| 1261 | 1261 | Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()), |
| 1262 | 1262 | other => other.clone(), |
| @@ -4450,7 +4450,7 @@ impl Lowerer { | ||
| 4450 | 4450 | elem = elem.or(v.ty.clone()); |
| 4451 | 4451 | parts.push(v.code); |
| 4452 | 4452 | } |
| 4453 | - let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t))); | |
| 4453 | + let t = elem.map(|t| Nim::Array(a.elems.len().to_string(), Box::new(t))); | |
| 4454 | 4454 | Ok(Val::new(format!("[{}]", parts.join(", ")), t)) |
| 4455 | 4455 | } |
| 4456 | 4456 | Expr::Repeat(r) => { |
| @@ -4465,9 +4465,21 @@ impl Lowerer { | ||
| 4465 | 4465 | }; |
| 4466 | 4466 | let v = self.expr_at(&r.expr, want_elem.as_ref())?; |
| 4467 | 4467 | if let Some(Nim::Array(n, _)) = expect { |
| 4468 | - let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect(); | |
| 4469 | - let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t))); | |
| 4470 | - return Ok(Val::new(format!("[{}]", elems.join(", ")), t)); | |
| 4468 | + let t = v.ty.clone().map(|t| Nim::Array(n.clone(), Box::new(t))); | |
| 4469 | + // Written out element by element when the count is a plain | |
| 4470 | + // literal, which reads better and is what most of these | |
| 4471 | + // are; otherwise filled by the prelude, which needs only | |
| 4472 | + // the type. | |
| 4473 | + return Ok(match n.parse::<usize>() { | |
| 4474 | + Ok(k) => { | |
| 4475 | + let elems: Vec<String> = (0..k).map(|_| v.code.clone()).collect(); | |
| 4476 | + Val::new(format!("[{}]", elems.join(", ")), t) | |
| 4477 | + } | |
| 4478 | + Err(_) => Val::new( | |
| 4479 | + format!("rsArrayFill[{}, {}]({})", n, ty::elem_of(&t), v.code), | |
| 4480 | + t, | |
| 4481 | + ), | |
| 4482 | + }); | |
| 4471 | 4483 | } |
| 4472 | 4484 | let n = self.expr(&r.len)?; |
| 4473 | 4485 | let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t))); |
| @@ -1256,7 +1256,7 @@ impl Lowerer { | |||
| 1256 | } | 1256 | } |
| 1257 | Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))), | 1257 | Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))), |
| 1258 | Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))), | 1258 | Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))), |
| 1259 | - Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))), | 1259 | + Nim::Array(n, e) => Nim::Array(n.clone(), Box::new(Self::subst(e, m))), |
| 1260 | Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))), | 1260 | Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))), |
| 1261 | Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()), | 1261 | Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()), |
| 1262 | other => other.clone(), | 1262 | other => other.clone(), |
| @@ -4450,7 +4450,7 @@ impl Lowerer { | |||
| 4450 | elem = elem.or(v.ty.clone()); | 4450 | elem = elem.or(v.ty.clone()); |
| 4451 | parts.push(v.code); | 4451 | parts.push(v.code); |
| 4452 | } | 4452 | } |
| 4453 | - let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t))); | 4453 | + let t = elem.map(|t| Nim::Array(a.elems.len().to_string(), Box::new(t))); |
| 4454 | Ok(Val::new(format!("[{}]", parts.join(", ")), t)) | 4454 | Ok(Val::new(format!("[{}]", parts.join(", ")), t)) |
| 4455 | } | 4455 | } |
| 4456 | Expr::Repeat(r) => { | 4456 | Expr::Repeat(r) => { |
| @@ -4465,9 +4465,21 @@ impl Lowerer { | |||
| 4465 | }; | 4465 | }; |
| 4466 | let v = self.expr_at(&r.expr, want_elem.as_ref())?; | 4466 | let v = self.expr_at(&r.expr, want_elem.as_ref())?; |
| 4467 | if let Some(Nim::Array(n, _)) = expect { | 4467 | if let Some(Nim::Array(n, _)) = expect { |
| 4468 | - let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect(); | 4468 | + let t = v.ty.clone().map(|t| Nim::Array(n.clone(), Box::new(t))); |
| 4469 | - let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t))); | 4469 | + // Written out element by element when the count is a plain |
| 4470 | - return Ok(Val::new(format!("[{}]", elems.join(", ")), t)); | 4470 | + // literal, which reads better and is what most of these |
| 4471 | + // are; otherwise filled by the prelude, which needs only | ||
| 4472 | + // the type. | ||
| 4473 | + return Ok(match n.parse::<usize>() { | ||
| 4474 | + Ok(k) => { | ||
| 4475 | + let elems: Vec<String> = (0..k).map(|_| v.code.clone()).collect(); | ||
| 4476 | + Val::new(format!("[{}]", elems.join(", ")), t) | ||
| 4477 | + } | ||
| 4478 | + Err(_) => Val::new( | ||
| 4479 | + format!("rsArrayFill[{}, {}]({})", n, ty::elem_of(&t), v.code), | ||
| 4480 | + t, | ||
| 4481 | + ), | ||
| 4482 | + }); | ||
| 4471 | } | 4483 | } |
| 4472 | let n = self.expr(&r.len)?; | 4484 | let n = self.expr(&r.len)?; |
| 4473 | let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t))); | 4485 | let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t))); |
modified
src/prelude.nim +6 -0 | @@ -313,3 +313,9 @@ proc rsBytes*(s: string): seq[uint8] = | ||
| 313 | 313 | proc newSeqWith*[T](n: int, v: T): seq[T] = |
| 314 | 314 | result = newSeq[T](n) |
| 315 | 315 | for i in 0 ..< n: result[i] = v |
| 316 | + | |
| 317 | +## `[v; N]` where `N` is a const expression rather than a literal. Nim's | |
| 318 | +## array literal has to be written out element by element, which needs the | |
| 319 | +## count at lowering time; this needs only the type. | |
| 320 | +proc rsArrayFill*[N: static int, T](v: T): array[N, T] = | |
| 321 | + for i in 0 ..< N: result[i] = v | |
| @@ -313,3 +313,9 @@ proc rsBytes*(s: string): seq[uint8] = | |||
| 313 | proc newSeqWith*[T](n: int, v: T): seq[T] = | 313 | proc newSeqWith*[T](n: int, v: T): seq[T] = |
| 314 | result = newSeq[T](n) | 314 | result = newSeq[T](n) |
| 315 | for i in 0 ..< n: result[i] = v | 315 | for i in 0 ..< n: result[i] = v |
| 316 | + | ||
| 317 | +## `[v; N]` where `N` is a const expression rather than a literal. Nim's | ||
| 318 | +## array literal has to be written out element by element, which needs the | ||
| 319 | +## count at lowering time; this needs only the type. | ||
| 320 | +proc rsArrayFill*[N: static int, T](v: T): array[N, T] = | ||
| 321 | + for i in 0 ..< N: result[i] = v | ||
modified
src/ty.rs +71 -9 | @@ -12,7 +12,11 @@ pub enum Nim { | ||
| 12 | 12 | Prim(String), |
| 13 | 13 | Seq(Box<Nim>), |
| 14 | 14 | OpenArray(Box<Nim>), |
| 15 | - Array(usize, Box<Nim>), | |
| 15 | + /// `[T; N]`. `N` is kept as a Nim *expression*, not a number: Rust writes | |
| 16 | + /// array lengths as const expressions (`[u8; BLOCK_LEN]`, `[u8; N * 2]`) | |
| 17 | + /// and Nim accepts the same, so resolving it to an integer here would only | |
| 18 | + /// lose information rustnim does not have at this point anyway. | |
| 19 | + Array(String, Box<Nim>), | |
| 16 | 20 | Tuple(Vec<Nim>), |
| 17 | 21 | Named(String, Vec<Nim>), |
| 18 | 22 | Var(Box<Nim>), |
| @@ -350,14 +354,14 @@ pub fn map(t: &Type) -> Result<Nim, String> { | ||
| 350 | 354 | } |
| 351 | 355 | Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), |
| 352 | 356 | Type::Array(a) => { |
| 353 | - let len = match &a.len { | |
| 354 | - syn::Expr::Lit(syn::ExprLit { | |
| 355 | - lit: syn::Lit::Int(i), | |
| 356 | - .. | |
| 357 | - }) => i | |
| 358 | - .base10_parse::<usize>() | |
| 359 | - .map_err(|e| format!("array length: {}", e))?, | |
| 360 | - _ => return Err("array length must be a literal".into()), | |
| 357 | + // Rust array lengths are `usize`; Nim wants an ordinal count, and | |
| 358 | + // a `uint` const used directly as one is a type mismatch. The | |
| 359 | + // conversion is const-folded, so this costs nothing at runtime. | |
| 360 | + let len = const_expr(&a.len)?; | |
| 361 | + let len = if len.chars().all(|c| c.is_ascii_digit()) { | |
| 362 | + len | |
| 363 | + } else { | |
| 364 | + format!("int({len})") | |
| 361 | 365 | }; |
| 362 | 366 | Ok(Nim::Array(len, Box::new(map(&a.elem)?))) |
| 363 | 367 | } |
| @@ -516,3 +520,61 @@ fn discriminant(t: &Type) -> &'static str { | ||
| 516 | 520 | _ => "other", |
| 517 | 521 | } |
| 518 | 522 | } |
| 523 | + | |
| 524 | +/// Render a Rust const expression as a Nim one, for an array length. | |
| 525 | +/// | |
| 526 | +/// The two languages agree on this sub-language: integer literals, a name | |
| 527 | +/// bound by a `const`, and the four arithmetic operators mean the same thing | |
| 528 | +/// and are written the same way. Anything outside it -- a const fn call, say -- | |
| 529 | +/// is reported rather than guessed at. | |
| 530 | +pub fn const_expr(e: &syn::Expr) -> Result<String, String> { | |
| 531 | + use syn::{BinOp, Expr}; | |
| 532 | + match e { | |
| 533 | + Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(i), .. }) => Ok(i.base10_digits().to_string()), | |
| 534 | + Expr::Paren(p) => Ok(format!("({})", const_expr(&p.expr)?)), | |
| 535 | + Expr::Group(g) => const_expr(&g.expr), | |
| 536 | + Expr::Path(p) => { | |
| 537 | + let seg = p | |
| 538 | + .path | |
| 539 | + .segments | |
| 540 | + .last() | |
| 541 | + .ok_or_else(|| "empty path in an array length".to_string())?; | |
| 542 | + Ok(seg.ident.to_string()) | |
| 543 | + } | |
| 544 | + Expr::Binary(b) => { | |
| 545 | + let op = match b.op { | |
| 546 | + BinOp::Add(_) => "+", | |
| 547 | + BinOp::Sub(_) => "-", | |
| 548 | + BinOp::Mul(_) => "*", | |
| 549 | + // Rust's `/` and `%` on integers are Nim's `div` and `mod`. | |
| 550 | + BinOp::Div(_) => "div", | |
| 551 | + BinOp::Rem(_) => "mod", | |
| 552 | + _ => { | |
| 553 | + return Err( | |
| 554 | + "only `+ - * / %` are supported in an array length".into() | |
| 555 | + ) | |
| 556 | + } | |
| 557 | + }; | |
| 558 | + Ok(format!( | |
| 559 | + "({} {} {})", | |
| 560 | + const_expr(&b.left)?, | |
| 561 | + op, | |
| 562 | + const_expr(&b.right)? | |
| 563 | + )) | |
| 564 | + } | |
| 565 | + Expr::Cast(c) => const_expr(&c.expr), | |
| 566 | + other => Err(format!( | |
| 567 | + "array length is not a const expression rustnim can render: `{}`", | |
| 568 | + quote::quote!(#other) | |
| 569 | + )), | |
| 570 | + } | |
| 571 | +} | |
| 572 | + | |
| 573 | +/// The element type of an `Array`, rendered. Used where Nim needs it spelled | |
| 574 | +/// out because it cannot infer it from a value alone. | |
| 575 | +pub fn elem_of(t: &Option<Nim>) -> String { | |
| 576 | + match t { | |
| 577 | + Some(Nim::Array(_, e)) => e.render(), | |
| 578 | + _ => "auto".into(), | |
| 579 | + } | |
| 580 | +} | |
| @@ -12,7 +12,11 @@ pub enum Nim { | |||
| 12 | Prim(String), | 12 | Prim(String), |
| 13 | Seq(Box<Nim>), | 13 | Seq(Box<Nim>), |
| 14 | OpenArray(Box<Nim>), | 14 | OpenArray(Box<Nim>), |
| 15 | - Array(usize, Box<Nim>), | 15 | + /// `[T; N]`. `N` is kept as a Nim *expression*, not a number: Rust writes |
| 16 | + /// array lengths as const expressions (`[u8; BLOCK_LEN]`, `[u8; N * 2]`) | ||
| 17 | + /// and Nim accepts the same, so resolving it to an integer here would only | ||
| 18 | + /// lose information rustnim does not have at this point anyway. | ||
| 19 | + Array(String, Box<Nim>), | ||
| 16 | Tuple(Vec<Nim>), | 20 | Tuple(Vec<Nim>), |
| 17 | Named(String, Vec<Nim>), | 21 | Named(String, Vec<Nim>), |
| 18 | Var(Box<Nim>), | 22 | Var(Box<Nim>), |
| @@ -350,14 +354,14 @@ pub fn map(t: &Type) -> Result<Nim, String> { | |||
| 350 | } | 354 | } |
| 351 | Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), | 355 | Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), |
| 352 | Type::Array(a) => { | 356 | Type::Array(a) => { |
| 353 | - let len = match &a.len { | 357 | + // Rust array lengths are `usize`; Nim wants an ordinal count, and |
| 354 | - syn::Expr::Lit(syn::ExprLit { | 358 | + // a `uint` const used directly as one is a type mismatch. The |
| 355 | - lit: syn::Lit::Int(i), | 359 | + // conversion is const-folded, so this costs nothing at runtime. |
| 356 | - .. | 360 | + let len = const_expr(&a.len)?; |
| 357 | - }) => i | 361 | + let len = if len.chars().all(|c| c.is_ascii_digit()) { |
| 358 | - .base10_parse::<usize>() | 362 | + len |
| 359 | - .map_err(|e| format!("array length: {}", e))?, | 363 | + } else { |
| 360 | - _ => return Err("array length must be a literal".into()), | 364 | + format!("int({len})") |
| 361 | }; | 365 | }; |
| 362 | Ok(Nim::Array(len, Box::new(map(&a.elem)?))) | 366 | Ok(Nim::Array(len, Box::new(map(&a.elem)?))) |
| 363 | } | 367 | } |
| @@ -516,3 +520,61 @@ fn discriminant(t: &Type) -> &'static str { | |||
| 516 | _ => "other", | 520 | _ => "other", |
| 517 | } | 521 | } |
| 518 | } | 522 | } |
| 523 | + | ||
| 524 | +/// Render a Rust const expression as a Nim one, for an array length. | ||
| 525 | +/// | ||
| 526 | +/// The two languages agree on this sub-language: integer literals, a name | ||
| 527 | +/// bound by a `const`, and the four arithmetic operators mean the same thing | ||
| 528 | +/// and are written the same way. Anything outside it -- a const fn call, say -- | ||
| 529 | +/// is reported rather than guessed at. | ||
| 530 | +pub fn const_expr(e: &syn::Expr) -> Result<String, String> { | ||
| 531 | + use syn::{BinOp, Expr}; | ||
| 532 | + match e { | ||
| 533 | + Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(i), .. }) => Ok(i.base10_digits().to_string()), | ||
| 534 | + Expr::Paren(p) => Ok(format!("({})", const_expr(&p.expr)?)), | ||
| 535 | + Expr::Group(g) => const_expr(&g.expr), | ||
| 536 | + Expr::Path(p) => { | ||
| 537 | + let seg = p | ||
| 538 | + .path | ||
| 539 | + .segments | ||
| 540 | + .last() | ||
| 541 | + .ok_or_else(|| "empty path in an array length".to_string())?; | ||
| 542 | + Ok(seg.ident.to_string()) | ||
| 543 | + } | ||
| 544 | + Expr::Binary(b) => { | ||
| 545 | + let op = match b.op { | ||
| 546 | + BinOp::Add(_) => "+", | ||
| 547 | + BinOp::Sub(_) => "-", | ||
| 548 | + BinOp::Mul(_) => "*", | ||
| 549 | + // Rust's `/` and `%` on integers are Nim's `div` and `mod`. | ||
| 550 | + BinOp::Div(_) => "div", | ||
| 551 | + BinOp::Rem(_) => "mod", | ||
| 552 | + _ => { | ||
| 553 | + return Err( | ||
| 554 | + "only `+ - * / %` are supported in an array length".into() | ||
| 555 | + ) | ||
| 556 | + } | ||
| 557 | + }; | ||
| 558 | + Ok(format!( | ||
| 559 | + "({} {} {})", | ||
| 560 | + const_expr(&b.left)?, | ||
| 561 | + op, | ||
| 562 | + const_expr(&b.right)? | ||
| 563 | + )) | ||
| 564 | + } | ||
| 565 | + Expr::Cast(c) => const_expr(&c.expr), | ||
| 566 | + other => Err(format!( | ||
| 567 | + "array length is not a const expression rustnim can render: `{}`", | ||
| 568 | + quote::quote!(#other) | ||
| 569 | + )), | ||
| 570 | + } | ||
| 571 | +} | ||
| 572 | + | ||
| 573 | +/// The element type of an `Array`, rendered. Used where Nim needs it spelled | ||
| 574 | +/// out because it cannot infer it from a value alone. | ||
| 575 | +pub fn elem_of(t: &Option<Nim>) -> String { | ||
| 576 | + match t { | ||
| 577 | + Some(Nim::Array(_, e)) => e.render(), | ||
| 578 | + _ => "auto".into(), | ||
| 579 | + } | ||
| 580 | +} | ||
added
tests/cases/052-const-array-len.rs +32 -0 | new file mode 100644 | ||
| @@ -0,0 +1,32 @@ | ||
| 1 | +// An array length written as a const expression, which is how Rust spells a | |
| 2 | +// buffer size. Nim accepts the same expression, so it is carried across as one | |
| 3 | +// rather than resolved to a number. | |
| 4 | +const BLOCK: usize = 4; | |
| 5 | +const DOUBLE: usize = BLOCK * 2; | |
| 6 | + | |
| 7 | +struct Buf { | |
| 8 | + data: [u8; BLOCK], | |
| 9 | + wide: [u8; DOUBLE], | |
| 10 | +} | |
| 11 | + | |
| 12 | +fn sum(b: &[u8]) -> u32 { | |
| 13 | + let mut t: u32 = 0; | |
| 14 | + for x in b { | |
| 15 | + t += *x as u32; | |
| 16 | + } | |
| 17 | + t | |
| 18 | +} | |
| 19 | + | |
| 20 | +fn main() { | |
| 21 | + let mut b = Buf { | |
| 22 | + data: [7u8; BLOCK], | |
| 23 | + wide: [1u8; DOUBLE], | |
| 24 | + }; | |
| 25 | + b.data[0] = 100; | |
| 26 | + b.wide[7] = 9; | |
| 27 | + println!("{} {}", b.data.len(), b.wide.len()); | |
| 28 | + println!("{} {}", sum(&b.data), sum(&b.wide)); | |
| 29 | + | |
| 30 | + let z: [u16; BLOCK + 1] = [3u16; BLOCK + 1]; | |
| 31 | + println!("{} {}", z.len(), z[4]); | |
| 32 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | +// An array length written as a const expression, which is how Rust spells a | ||
| 2 | +// buffer size. Nim accepts the same expression, so it is carried across as one | ||
| 3 | +// rather than resolved to a number. | ||
| 4 | +const BLOCK: usize = 4; | ||
| 5 | +const DOUBLE: usize = BLOCK * 2; | ||
| 6 | + | ||
| 7 | +struct Buf { | ||
| 8 | + data: [u8; BLOCK], | ||
| 9 | + wide: [u8; DOUBLE], | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +fn sum(b: &[u8]) -> u32 { | ||
| 13 | + let mut t: u32 = 0; | ||
| 14 | + for x in b { | ||
| 15 | + t += *x as u32; | ||
| 16 | + } | ||
| 17 | + t | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +fn main() { | ||
| 21 | + let mut b = Buf { | ||
| 22 | + data: [7u8; BLOCK], | ||
| 23 | + wide: [1u8; DOUBLE], | ||
| 24 | + }; | ||
| 25 | + b.data[0] = 100; | ||
| 26 | + b.wide[7] = 9; | ||
| 27 | + println!("{} {}", b.data.len(), b.wide.len()); | ||
| 28 | + println!("{} {}", sum(&b.data), sum(&b.wide)); | ||
| 29 | + | ||
| 30 | + let z: [u16; BLOCK + 1] = [3u16; BLOCK + 1]; | ||
| 31 | + println!("{} {}", z.len(), z[4]); | ||
| 32 | +} | ||