nandi/rustnimpublic Fork
⑂ 026a641
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

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>
nandithebull committed 2026-09-19T09:40:54-07:00 Browse files
026a641 parent: 32c48d0
modified src/lower.rs +17 -5
@@ -1256,7 +1256,7 @@ impl Lowerer {
12561256 }
12571257 Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))),
12581258 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))),
12601260 Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))),
12611261 Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()),
12621262 other => other.clone(),
@@ -4450,7 +4450,7 @@ impl Lowerer {
44504450 elem = elem.or(v.ty.clone());
44514451 parts.push(v.code);
44524452 }
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)));
44544454 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
44554455 }
44564456 Expr::Repeat(r) => {
@@ -4465,9 +4465,21 @@ impl Lowerer {
44654465 };
44664466 let v = self.expr_at(&r.expr, want_elem.as_ref())?;
44674467 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+ });
44714483 }
44724484 let n = self.expr(&r.len)?;
44734485 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] =
313313 proc newSeqWith*[T](n: int, v: T): seq[T] =
314314 result = newSeq[T](n)
315315 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] = v315 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 {
1212 Prim(String),
1313 Seq(Box<Nim>),
1414 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>),
1620 Tuple(Vec<Nim>),
1721 Named(String, Vec<Nim>),
1822 Var(Box<Nim>),
@@ -350,14 +354,14 @@ pub fn map(t: &Type) -> Result<Nim, String> {
350354 }
351355 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
352356 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})")
361365 };
362366 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
363367 }
@@ -516,3 +520,61 @@ fn discriminant(t: &Type) -> &'static str {
516520 _ => "other",
517521 }
518522 }
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- }) => i361+ 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+}