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

Implement const generics; Nim's `static` parameter is the same idea

Three places said "has a const generic parameter, which Nim has no equivalent
for". Nim's `static` generic parameter is exactly that: a generic parameter
that is a value rather than a type. `struct Chunk<const N: usize>` is
`type Chunk[N: static int]`, and the parameter behaves like any other
everywhere else, so only the declaration differs.

Four things had to follow:

  * `Chunk<4>` -- a const *argument* -- was being dropped, because only
    `GenericArgument::Type` was mapped. Nim writes the value in the same
    position, so it is carried across as the expression it is.
  * A parameter appearing only in the return type cannot be inferred from a
    call. Rust reads it off the annotation at the binding; Nim will not, so
    `explicit_generics` writes it out -- and only when every parameter can be
    resolved, since Nim takes these positionally.
  * `rsArrayFill` became a template. As a generic proc its own `N` was no more
    inferable than the caller's.
  * `Foo::new` now lowers to `new_r`. Nim's `system.new[T](a: var ref T)` wins
    overload resolution against a generated generic `new` and reports the
    failure from inside `system`, which is a baffling place to find it. This
    renames every `new` in every existing case; all 57 still pass.

Found by the 3.0M-line dependency run, on `chacha20`'s `struct SlimSSSE3`.

tests/cases/054.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T09:51:05-07:00 Browse files
b28f72c parent: f7a0d53
modified src/lower.rs +112 -33
@@ -27,8 +27,14 @@ const NIM_KEYWORDS: &[&str] = &[
2727 "using", "var", "when", "while", "xor", "result", "echo",
2828 ];
2929
30+/// Not keywords, but `system` procs whose own generic signatures win overload
31+/// resolution against a generated one. `Foo::new` is the common case: Nim's
32+/// `new[T](a: var ref T)` swallows a generic `new` and reports the failure
33+/// inside `system`, which is a confusing place for it to surface.
34+const NIM_SHADOWED: &[&str] = &["new"];
35+
3036 fn ident(name: &str) -> String {
31- if NIM_KEYWORDS.contains(&name) {
37+ if NIM_KEYWORDS.contains(&name) || NIM_SHADOWED.contains(&name) {
3238 return format!("{name}_r");
3339 }
3440 // Nim identifiers may not begin with an underscore, and may not contain
@@ -308,6 +314,9 @@ pub struct Lowerer {
308314 /// turn into an error.
309315 local_items: HashMap<String, String>,
310316 emitted_local: std::collections::HashSet<String>,
317+ /// Const generic parameters, by name, with their Nim type. Nim's `static`
318+ /// parameter is the same thing: a generic parameter that is a value.
319+ const_gens: HashMap<String, String>,
311320 /// Module names supplied as separate input files. A `mod x;` naming one
312321 /// of these is satisfied by that file having been passed in.
313322 pub modules: Vec<String>,
@@ -381,6 +390,7 @@ impl Lowerer {
381390 uses_i128: std::cell::Cell::new(false),
382391 local_items: HashMap::new(),
383392 emitted_local: std::collections::HashSet::new(),
393+ const_gens: HashMap::new(),
384394 modules: Vec::new(),
385395 emitted: 0,
386396 features: Vec::new(),
@@ -644,6 +654,7 @@ impl Lowerer {
644654 // A body may declare its own types, and a signature below may
645655 // name one, so these come first.
646656 self.collect_body_items(&f.block)?;
657+ self.note_const_generics(&f.sig.generics)?;
647658 let (params, ret) = self.signature(&f.sig)?;
648659 if matches!(ret, Nim::Future(_)) {
649660 self.uses_async = true;
@@ -657,13 +668,7 @@ impl Lowerer {
657668 .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() });
658669 }
659670 Item::Struct(s) => {
660- if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
661- return Err(format!(
662- "`struct {}` has a const generic parameter, which Nim has \
663- no equivalent for",
664- s.ident
665- ));
666- }
671+ self.note_const_generics(&s.generics)?;
667672 let g = Self::generics_of(&s.generics);
668673 // The parameters must be in scope while the field types are
669674 // mapped, so that `T` resolves to itself rather than to an
@@ -738,12 +743,7 @@ impl Lowerer {
738743 }
739744 Item::Enum(e) => {
740745 let name = e.ident.to_string();
741- if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
742- return Err(format!(
743- "`enum {name}` has a const generic parameter, which Nim \
744- has no equivalent for"
745- ));
746- }
746+ self.note_const_generics(&e.generics)?;
747747 self.type_generics
748748 .insert(name.clone(), Self::generics_of(&e.generics));
749749
@@ -828,9 +828,11 @@ impl Lowerer {
828828 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
829829 // Before anything else: a method body may declare its own types, and
830830 // the trait branches below return early.
831+ self.note_const_generics(&im.generics)?;
831832 for it in &im.items {
832833 if let syn::ImplItem::Fn(m) = it {
833834 self.collect_body_items(&m.block)?;
835+ self.note_const_generics(&m.sig.generics)?;
834836 }
835837 }
836838 {
@@ -1188,11 +1190,42 @@ impl Lowerer {
11881190 .iter()
11891191 .filter_map(|p| match p {
11901192 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
1193+ // A const parameter is a generic parameter too. Nim spells it
1194+ // `N: static int`; the name behaves the same way everywhere
1195+ // else, so only the declaration differs. See `gen_list`.
1196+ syn::GenericParam::Const(c) => Some(c.ident.to_string()),
11911197 _ => None,
11921198 })
11931199 .collect()
11941200 }
11951201
1202+ /// Record the Nim type of each const generic parameter, so `gen_list` can
1203+ /// declare it as `static`.
1204+ fn note_const_generics(&mut self, g: &syn::Generics) -> Result<(), String> {
1205+ for p in &g.params {
1206+ let syn::GenericParam::Const(c) = p else { continue };
1207+ let t = ty::map(&c.ty)?;
1208+ // Nim's `static int` is the ordinary spelling and what an array
1209+ // length wants; `usize` would otherwise arrive as `uint`.
1210+ let rendered = match &t {
1211+ Nim::Prim(p) if p == "uint" || p == "int" => "int".to_string(),
1212+ other => other.render(),
1213+ };
1214+ let name = c.ident.to_string();
1215+ if let Some(prev) = self.const_gens.get(&name) {
1216+ if prev != &rendered {
1217+ return Err(format!(
1218+ "const generic parameter `{name}` is declared with two \
1219+ different types (`{prev}` and `{rendered}`); Nim has \
1220+ one namespace here, so one would shadow the other"
1221+ ));
1222+ }
1223+ }
1224+ self.const_gens.insert(name, rendered);
1225+ }
1226+ Ok(())
1227+ }
1228+
11961229 /// Bind a signature's type parameters by matching its declared parameter
11971230 /// types against the actual argument types, then substitute into `ret`.
11981231 ///
@@ -1317,12 +1350,54 @@ impl Lowerer {
13171350 }
13181351
13191352 /// `[T, U]`, or empty.
1320- fn gen_list(params: &[String]) -> String {
1353+ /// The explicit generic-argument list a call needs, or empty.
1354+ ///
1355+ /// Only produced when the callee has a const generic parameter that the
1356+ /// arguments do not determine *and* every parameter can be resolved --
1357+ /// Nim takes these positionally, so a partial list is not an option.
1358+ fn explicit_generics(&self, sig: &Sig, args: &[Option<Nim>], expect: Option<&Nim>) -> String {
1359+ if sig.generics.is_empty() {
1360+ return String::new();
1361+ }
1362+ let mut bound: HashMap<String, Nim> = HashMap::new();
1363+ for (decl, actual) in sig.params.iter().zip(args) {
1364+ if let Some(a) = actual {
1365+ Self::unify(decl, a, &sig.generics, &mut bound);
1366+ }
1367+ }
1368+ let missing: Vec<&String> = sig
1369+ .generics
1370+ .iter()
1371+ .filter(|g| !bound.contains_key(*g))
1372+ .collect();
1373+ if missing.is_empty() || !missing.iter().any(|g| self.const_gens.contains_key(*g)) {
1374+ return String::new();
1375+ }
1376+ if let Some(e) = expect {
1377+ Self::unify(&sig.ret, e, &sig.generics, &mut bound);
1378+ }
1379+ let mut out = Vec::new();
1380+ for g in &sig.generics {
1381+ match bound.get(g) {
1382+ Some(t) => out.push(t.render()),
1383+ None => return String::new(),
1384+ }
1385+ }
1386+ format!("[{}]", out.join(", "))
1387+ }
1388+
1389+ fn gen_list(&self, params: &[String]) -> String {
13211390 if params.is_empty() {
1322- String::new()
1323- } else {
1324- format!("[{}]", params.join(", "))
1391+ return String::new();
13251392 }
1393+ let rendered: Vec<String> = params
1394+ .iter()
1395+ .map(|p| match self.const_gens.get(p) {
1396+ Some(t) => format!("{}: static {}", ident(p), t),
1397+ None => p.clone(),
1398+ })
1399+ .collect();
1400+ format!("[{}]", rendered.join(", "))
13261401 }
13271402
13281403 /// The Nim name for a function, qualified by its module.
@@ -1377,7 +1452,7 @@ impl Lowerer {
13771452 params.push(g);
13781453 }
13791454 }
1380- let gens = Self::gen_list(&params);
1455+ let gens = self.gen_list(&params);
13811456 let mut parts = Vec::new();
13821457 if let Some(self_ty) = recv {
13831458 let mutable = matches!(
@@ -1517,15 +1592,9 @@ impl Lowerer {
15171592 // `unsafe fn` marks a contract for callers; it does not change what
15181593 // the body means, so it lowers like any other proc.
15191594 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
1520- // they disappear. Type parameters become Nim generic parameters.
1521- // Const parameters have no Nim equivalent and are still rejected.
1522- if sig.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
1523- return Err(format!(
1524- "`fn {}` has a const generic parameter, which Nim has no \
1525- equivalent for",
1526- sig.ident
1527- ));
1528- }
1595+ // they disappear. Type parameters become Nim generic parameters, and
1596+ // a const parameter becomes a `static` one, which is the same idea:
1597+ // a generic parameter that is a value rather than a type.
15291598 let mut params = Vec::new();
15301599 let mut fresh = 0usize;
15311600 for a in &sig.inputs {
@@ -1647,7 +1716,7 @@ impl Lowerer {
16471716 Item::Struct(s) => {
16481717 let name = s.ident.to_string();
16491718 let fields = self.structs[&name].clone();
1650- let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1719+ let g = self.gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
16511720 self.line(&format!("type {}*{} = object", ident(&name), g));
16521721 self.indent += 1;
16531722 if fields.is_empty() {
@@ -2159,7 +2228,7 @@ impl Lowerer {
21592228
21602229 fn emit_enum(&mut self, def: &EnumDef) {
21612230 let name = ident(&def.name);
2162- let g = Self::gen_list(
2231+ let g = self.gen_list(
21632232 self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
21642233 );
21652234 if def.simple && !g.is_empty() {
@@ -2489,7 +2558,7 @@ impl Lowerer {
24892558 gparams.push(g);
24902559 }
24912560 }
2492- let gens = Self::gen_list(&gparams);
2561+ let gens = self.gen_list(&gparams);
24932562 let outer_fg = std::mem::replace(&mut self.fn_generics, gparams.clone());
24942563 let head = if ret == Nim::Unit {
24952564 format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
@@ -4492,7 +4561,9 @@ impl Lowerer {
44924561 Val::new(format!("[{}]", elems.join(", ")), t)
44934562 }
44944563 Err(_) => Val::new(
4495- format!("rsArrayFill[{}, {}]({})", n, ty::elem_of(&t), v.code),
4564+ // Rust array lengths are `usize`; Nim wants an
4565+ // ordinal count. The conversion is const-folded.
4566+ format!("rsArrayFill(int({}), {}, {})", n, ty::elem_of(&t), v.code),
44964567 t,
44974568 ),
44984569 });
@@ -5623,7 +5694,15 @@ impl Lowerer {
56235694 .get(&(q.clone(), name.clone()))
56245695 .cloned()
56255696 .unwrap_or_else(|| ident(&name));
5626- return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
5697+ // A parameter that appears only in the return type cannot be
5698+ // inferred from the call. Rust reads it off the annotation at
5699+ // the binding; Nim will not, so it is written out. `Chunk::new`
5700+ // with `N` only in `Chunk<N>` is the shape that needs this.
5701+ let explicit = self.explicit_generics(sig, &arg_tys, expect);
5702+ return Ok(Val::new(
5703+ format!("{}{}({})", nim, explicit, codes.join(", ")),
5704+ Some(ret),
5705+ ));
56275706 }
56285707 }
56295708 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
@@ -27,8 +27,14 @@ const NIM_KEYWORDS: &[&str] = &[
27 "using", "var", "when", "while", "xor", "result", "echo",27 "using", "var", "when", "while", "xor", "result", "echo",
28 ];28 ];
29 29
30+/// Not keywords, but `system` procs whose own generic signatures win overload
31+/// resolution against a generated one. `Foo::new` is the common case: Nim's
32+/// `new[T](a: var ref T)` swallows a generic `new` and reports the failure
33+/// inside `system`, which is a confusing place for it to surface.
34+const NIM_SHADOWED: &[&str] = &["new"];
35+
30 fn ident(name: &str) -> String {36 fn ident(name: &str) -> String {
31- if NIM_KEYWORDS.contains(&name) {37+ if NIM_KEYWORDS.contains(&name) || NIM_SHADOWED.contains(&name) {
32 return format!("{name}_r");38 return format!("{name}_r");
33 }39 }
34 // Nim identifiers may not begin with an underscore, and may not contain40 // Nim identifiers may not begin with an underscore, and may not contain
@@ -308,6 +314,9 @@ pub struct Lowerer {
308 /// turn into an error.314 /// turn into an error.
309 local_items: HashMap<String, String>,315 local_items: HashMap<String, String>,
310 emitted_local: std::collections::HashSet<String>,316 emitted_local: std::collections::HashSet<String>,
317+ /// Const generic parameters, by name, with their Nim type. Nim's `static`
318+ /// parameter is the same thing: a generic parameter that is a value.
319+ const_gens: HashMap<String, String>,
311 /// Module names supplied as separate input files. A `mod x;` naming one320 /// Module names supplied as separate input files. A `mod x;` naming one
312 /// of these is satisfied by that file having been passed in.321 /// of these is satisfied by that file having been passed in.
313 pub modules: Vec<String>,322 pub modules: Vec<String>,
@@ -381,6 +390,7 @@ impl Lowerer {
381 uses_i128: std::cell::Cell::new(false),390 uses_i128: std::cell::Cell::new(false),
382 local_items: HashMap::new(),391 local_items: HashMap::new(),
383 emitted_local: std::collections::HashSet::new(),392 emitted_local: std::collections::HashSet::new(),
393+ const_gens: HashMap::new(),
384 modules: Vec::new(),394 modules: Vec::new(),
385 emitted: 0,395 emitted: 0,
386 features: Vec::new(),396 features: Vec::new(),
@@ -644,6 +654,7 @@ impl Lowerer {
644 // A body may declare its own types, and a signature below may654 // A body may declare its own types, and a signature below may
645 // name one, so these come first.655 // name one, so these come first.
646 self.collect_body_items(&f.block)?;656 self.collect_body_items(&f.block)?;
657+ self.note_const_generics(&f.sig.generics)?;
647 let (params, ret) = self.signature(&f.sig)?;658 let (params, ret) = self.signature(&f.sig)?;
648 if matches!(ret, Nim::Future(_)) {659 if matches!(ret, Nim::Future(_)) {
649 self.uses_async = true;660 self.uses_async = true;
@@ -657,13 +668,7 @@ impl Lowerer {
657 .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() });668 .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() });
658 }669 }
659 Item::Struct(s) => {670 Item::Struct(s) => {
660- if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {671+ self.note_const_generics(&s.generics)?;
661- return Err(format!(
662- "`struct {}` has a const generic parameter, which Nim has \
663- no equivalent for",
664- s.ident
665- ));
666- }
667 let g = Self::generics_of(&s.generics);672 let g = Self::generics_of(&s.generics);
668 // The parameters must be in scope while the field types are673 // The parameters must be in scope while the field types are
669 // mapped, so that `T` resolves to itself rather than to an674 // mapped, so that `T` resolves to itself rather than to an
@@ -738,12 +743,7 @@ impl Lowerer {
738 }743 }
739 Item::Enum(e) => {744 Item::Enum(e) => {
740 let name = e.ident.to_string();745 let name = e.ident.to_string();
741- if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {746+ self.note_const_generics(&e.generics)?;
742- return Err(format!(
743- "`enum {name}` has a const generic parameter, which Nim \
744- has no equivalent for"
745- ));
746- }
747 self.type_generics747 self.type_generics
748 .insert(name.clone(), Self::generics_of(&e.generics));748 .insert(name.clone(), Self::generics_of(&e.generics));
749 749
@@ -828,9 +828,11 @@ impl Lowerer {
828 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {828 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
829 // Before anything else: a method body may declare its own types, and829 // Before anything else: a method body may declare its own types, and
830 // the trait branches below return early.830 // the trait branches below return early.
831+ self.note_const_generics(&im.generics)?;
831 for it in &im.items {832 for it in &im.items {
832 if let syn::ImplItem::Fn(m) = it {833 if let syn::ImplItem::Fn(m) = it {
833 self.collect_body_items(&m.block)?;834 self.collect_body_items(&m.block)?;
835+ self.note_const_generics(&m.sig.generics)?;
834 }836 }
835 }837 }
836 {838 {
@@ -1188,11 +1190,42 @@ impl Lowerer {
1188 .iter()1190 .iter()
1189 .filter_map(|p| match p {1191 .filter_map(|p| match p {
1190 syn::GenericParam::Type(t) => Some(t.ident.to_string()),1192 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
1193+ // A const parameter is a generic parameter too. Nim spells it
1194+ // `N: static int`; the name behaves the same way everywhere
1195+ // else, so only the declaration differs. See `gen_list`.
1196+ syn::GenericParam::Const(c) => Some(c.ident.to_string()),
1191 _ => None,1197 _ => None,
1192 })1198 })
1193 .collect()1199 .collect()
1194 }1200 }
1195 1201
1202+ /// Record the Nim type of each const generic parameter, so `gen_list` can
1203+ /// declare it as `static`.
1204+ fn note_const_generics(&mut self, g: &syn::Generics) -> Result<(), String> {
1205+ for p in &g.params {
1206+ let syn::GenericParam::Const(c) = p else { continue };
1207+ let t = ty::map(&c.ty)?;
1208+ // Nim's `static int` is the ordinary spelling and what an array
1209+ // length wants; `usize` would otherwise arrive as `uint`.
1210+ let rendered = match &t {
1211+ Nim::Prim(p) if p == "uint" || p == "int" => "int".to_string(),
1212+ other => other.render(),
1213+ };
1214+ let name = c.ident.to_string();
1215+ if let Some(prev) = self.const_gens.get(&name) {
1216+ if prev != &rendered {
1217+ return Err(format!(
1218+ "const generic parameter `{name}` is declared with two \
1219+ different types (`{prev}` and `{rendered}`); Nim has \
1220+ one namespace here, so one would shadow the other"
1221+ ));
1222+ }
1223+ }
1224+ self.const_gens.insert(name, rendered);
1225+ }
1226+ Ok(())
1227+ }
1228+
1196 /// Bind a signature's type parameters by matching its declared parameter1229 /// Bind a signature's type parameters by matching its declared parameter
1197 /// types against the actual argument types, then substitute into `ret`.1230 /// types against the actual argument types, then substitute into `ret`.
1198 ///1231 ///
@@ -1317,12 +1350,54 @@ impl Lowerer {
1317 }1350 }
1318 1351
1319 /// `[T, U]`, or empty.1352 /// `[T, U]`, or empty.
1320- fn gen_list(params: &[String]) -> String {1353+ /// The explicit generic-argument list a call needs, or empty.
1354+ ///
1355+ /// Only produced when the callee has a const generic parameter that the
1356+ /// arguments do not determine *and* every parameter can be resolved --
1357+ /// Nim takes these positionally, so a partial list is not an option.
1358+ fn explicit_generics(&self, sig: &Sig, args: &[Option<Nim>], expect: Option<&Nim>) -> String {
1359+ if sig.generics.is_empty() {
1360+ return String::new();
1361+ }
1362+ let mut bound: HashMap<String, Nim> = HashMap::new();
1363+ for (decl, actual) in sig.params.iter().zip(args) {
1364+ if let Some(a) = actual {
1365+ Self::unify(decl, a, &sig.generics, &mut bound);
1366+ }
1367+ }
1368+ let missing: Vec<&String> = sig
1369+ .generics
1370+ .iter()
1371+ .filter(|g| !bound.contains_key(*g))
1372+ .collect();
1373+ if missing.is_empty() || !missing.iter().any(|g| self.const_gens.contains_key(*g)) {
1374+ return String::new();
1375+ }
1376+ if let Some(e) = expect {
1377+ Self::unify(&sig.ret, e, &sig.generics, &mut bound);
1378+ }
1379+ let mut out = Vec::new();
1380+ for g in &sig.generics {
1381+ match bound.get(g) {
1382+ Some(t) => out.push(t.render()),
1383+ None => return String::new(),
1384+ }
1385+ }
1386+ format!("[{}]", out.join(", "))
1387+ }
1388+
1389+ fn gen_list(&self, params: &[String]) -> String {
1321 if params.is_empty() {1390 if params.is_empty() {
1322- String::new()1391+ return String::new();
1323- } else {
1324- format!("[{}]", params.join(", "))
1325 }1392 }
1393+ let rendered: Vec<String> = params
1394+ .iter()
1395+ .map(|p| match self.const_gens.get(p) {
1396+ Some(t) => format!("{}: static {}", ident(p), t),
1397+ None => p.clone(),
1398+ })
1399+ .collect();
1400+ format!("[{}]", rendered.join(", "))
1326 }1401 }
1327 1402
1328 /// The Nim name for a function, qualified by its module.1403 /// The Nim name for a function, qualified by its module.
@@ -1377,7 +1452,7 @@ impl Lowerer {
1377 params.push(g);1452 params.push(g);
1378 }1453 }
1379 }1454 }
1380- let gens = Self::gen_list(&params);1455+ let gens = self.gen_list(&params);
1381 let mut parts = Vec::new();1456 let mut parts = Vec::new();
1382 if let Some(self_ty) = recv {1457 if let Some(self_ty) = recv {
1383 let mutable = matches!(1458 let mutable = matches!(
@@ -1517,15 +1592,9 @@ impl Lowerer {
1517 // `unsafe fn` marks a contract for callers; it does not change what1592 // `unsafe fn` marks a contract for callers; it does not change what
1518 // the body means, so it lowers like any other proc.1593 // the body means, so it lowers like any other proc.
1519 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so1594 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
1520- // they disappear. Type parameters become Nim generic parameters.1595+ // they disappear. Type parameters become Nim generic parameters, and
1521- // Const parameters have no Nim equivalent and are still rejected.1596+ // a const parameter becomes a `static` one, which is the same idea:
1522- if sig.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {1597+ // a generic parameter that is a value rather than a type.
1523- return Err(format!(
1524- "`fn {}` has a const generic parameter, which Nim has no \
1525- equivalent for",
1526- sig.ident
1527- ));
1528- }
1529 let mut params = Vec::new();1598 let mut params = Vec::new();
1530 let mut fresh = 0usize;1599 let mut fresh = 0usize;
1531 for a in &sig.inputs {1600 for a in &sig.inputs {
@@ -1647,7 +1716,7 @@ impl Lowerer {
1647 Item::Struct(s) => {1716 Item::Struct(s) => {
1648 let name = s.ident.to_string();1717 let name = s.ident.to_string();
1649 let fields = self.structs[&name].clone();1718 let fields = self.structs[&name].clone();
1650- let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));1719+ let g = self.gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1651 self.line(&format!("type {}*{} = object", ident(&name), g));1720 self.line(&format!("type {}*{} = object", ident(&name), g));
1652 self.indent += 1;1721 self.indent += 1;
1653 if fields.is_empty() {1722 if fields.is_empty() {
@@ -2159,7 +2228,7 @@ impl Lowerer {
2159 2228
2160 fn emit_enum(&mut self, def: &EnumDef) {2229 fn emit_enum(&mut self, def: &EnumDef) {
2161 let name = ident(&def.name);2230 let name = ident(&def.name);
2162- let g = Self::gen_list(2231+ let g = self.gen_list(
2163 self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),2232 self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
2164 );2233 );
2165 if def.simple && !g.is_empty() {2234 if def.simple && !g.is_empty() {
@@ -2489,7 +2558,7 @@ impl Lowerer {
2489 gparams.push(g);2558 gparams.push(g);
2490 }2559 }
2491 }2560 }
2492- let gens = Self::gen_list(&gparams);2561+ let gens = self.gen_list(&gparams);
2493 let outer_fg = std::mem::replace(&mut self.fn_generics, gparams.clone());2562 let outer_fg = std::mem::replace(&mut self.fn_generics, gparams.clone());
2494 let head = if ret == Nim::Unit {2563 let head = if ret == Nim::Unit {
2495 format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))2564 format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
@@ -4492,7 +4561,9 @@ impl Lowerer {
4492 Val::new(format!("[{}]", elems.join(", ")), t)4561 Val::new(format!("[{}]", elems.join(", ")), t)
4493 }4562 }
4494 Err(_) => Val::new(4563 Err(_) => Val::new(
4495- format!("rsArrayFill[{}, {}]({})", n, ty::elem_of(&t), v.code),4564+ // Rust array lengths are `usize`; Nim wants an
4565+ // ordinal count. The conversion is const-folded.
4566+ format!("rsArrayFill(int({}), {}, {})", n, ty::elem_of(&t), v.code),
4496 t,4567 t,
4497 ),4568 ),
4498 });4569 });
@@ -5623,7 +5694,15 @@ impl Lowerer {
5623 .get(&(q.clone(), name.clone()))5694 .get(&(q.clone(), name.clone()))
5624 .cloned()5695 .cloned()
5625 .unwrap_or_else(|| ident(&name));5696 .unwrap_or_else(|| ident(&name));
5626- return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));5697+ // A parameter that appears only in the return type cannot be
5698+ // inferred from the call. Rust reads it off the annotation at
5699+ // the binding; Nim will not, so it is written out. `Chunk::new`
5700+ // with `N` only in `Chunk<N>` is the shape that needs this.
5701+ let explicit = self.explicit_generics(sig, &arg_tys, expect);
5702+ return Ok(Val::new(
5703+ format!("{}{}({})", nim, explicit, codes.join(", ")),
5704+ Some(ret),
5705+ ));
5627 }5706 }
5628 }5707 }
5629 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();5708 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
modified src/prelude.nim +5 -2
@@ -317,5 +317,8 @@ proc newSeqWith*[T](n: int, v: T): seq[T] =
317317 ## `[v; N]` where `N` is a const expression rather than a literal. Nim's
318318 ## array literal has to be written out element by element, which needs the
319319 ## 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
320+template rsArrayFill*(n: static int, T: typedesc, v: untyped): untyped =
321+ block:
322+ var a: array[n, T]
323+ for i in 0 ..< a.len: a[i] = v
324+ a
@@ -317,5 +317,8 @@ proc newSeqWith*[T](n: int, v: T): seq[T] =
317 ## `[v; N]` where `N` is a const expression rather than a literal. Nim's317 ## `[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 the318 ## array literal has to be written out element by element, which needs the
319 ## count at lowering time; this needs only the type.319 ## count at lowering time; this needs only the type.
320-proc rsArrayFill*[N: static int, T](v: T): array[N, T] =320+template rsArrayFill*(n: static int, T: typedesc, v: untyped): untyped =
321- for i in 0 ..< N: result[i] = v321+ block:
322+ var a: array[n, T]
323+ for i in 0 ..< a.len: a[i] = v
324+ a
modified src/ty.rs +4 -8
@@ -286,6 +286,10 @@ pub fn map(t: &Type) -> Result<Nim, String> {
286286 .iter()
287287 .filter_map(|g| match g {
288288 GenericArgument::Type(t) => Some(map(t)),
289+ // A const argument -- `Chunk<4>`. Nim writes the value
290+ // in the same position, so it is carried across as the
291+ // expression it is.
292+ GenericArgument::Const(e) => Some(const_expr(e).map(Nim::Prim)),
289293 _ => None,
290294 })
291295 .collect::<Result<_, _>>()?,
@@ -354,15 +358,7 @@ pub fn map(t: &Type) -> Result<Nim, String> {
354358 }
355359 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
356360 Type::Array(a) => {
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.
360361 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})")
365- };
366362 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
367363 }
368364 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),
@@ -286,6 +286,10 @@ pub fn map(t: &Type) -> Result<Nim, String> {
286 .iter()286 .iter()
287 .filter_map(|g| match g {287 .filter_map(|g| match g {
288 GenericArgument::Type(t) => Some(map(t)),288 GenericArgument::Type(t) => Some(map(t)),
289+ // A const argument -- `Chunk<4>`. Nim writes the value
290+ // in the same position, so it is carried across as the
291+ // expression it is.
292+ GenericArgument::Const(e) => Some(const_expr(e).map(Nim::Prim)),
289 _ => None,293 _ => None,
290 })294 })
291 .collect::<Result<_, _>>()?,295 .collect::<Result<_, _>>()?,
@@ -354,15 +358,7 @@ pub fn map(t: &Type) -> Result<Nim, String> {
354 }358 }
355 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),359 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
356 Type::Array(a) => {360 Type::Array(a) => {
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 = const_expr(&a.len)?;
361- let len = if len.chars().all(|c| c.is_ascii_digit()) {
362- len
363- } else {
364- format!("int({len})")
365- };
366 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))362 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
367 }363 }
368 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),364 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),
added tests/cases/054-const-generics.rs +32 -0
new file mode 100644
@@ -0,0 +1,32 @@
1+// Const generics. Nim's `static` parameter is the same idea: a generic
2+// parameter that is a value rather than a type.
3+struct Chunk<const N: usize> {
4+ data: [u8; N],
5+}
6+
7+impl<const N: usize> Chunk<N> {
8+ fn new(fill: u8) -> Chunk<N> {
9+ Chunk { data: [fill; N] }
10+ }
11+
12+ fn total(&self) -> u32 {
13+ let mut t: u32 = 0;
14+ let mut i: usize = 0;
15+ while i < N {
16+ t += self.data[i] as u32;
17+ i += 1;
18+ }
19+ t
20+ }
21+}
22+
23+fn width<const N: usize>(_c: &Chunk<N>) -> usize {
24+ N
25+}
26+
27+fn main() {
28+ let a: Chunk<4> = Chunk::new(3);
29+ let b: Chunk<7> = Chunk::new(2);
30+ println!("{} {}", width(&a), a.total());
31+ println!("{} {}", width(&b), b.total());
32+}
new file mode 100644
@@ -0,0 +1,32 @@
1+// Const generics. Nim's `static` parameter is the same idea: a generic
2+// parameter that is a value rather than a type.
3+struct Chunk<const N: usize> {
4+ data: [u8; N],
5+}
6+
7+impl<const N: usize> Chunk<N> {
8+ fn new(fill: u8) -> Chunk<N> {
9+ Chunk { data: [fill; N] }
10+ }
11+
12+ fn total(&self) -> u32 {
13+ let mut t: u32 = 0;
14+ let mut i: usize = 0;
15+ while i < N {
16+ t += self.data[i] as u32;
17+ i += 1;
18+ }
19+ t
20+ }
21+}
22+
23+fn width<const N: usize>(_c: &Chunk<N>) -> usize {
24+ N
25+}
26+
27+fn main() {
28+ let a: Chunk<4> = Chunk::new(3);
29+ let b: Chunk<7> = Chunk::new(2);
30+ println!("{} {}", width(&a), a.total());
31+ println!("{} {}", width(&b), b.total());
32+}