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>b28f72c parent: f7a0d53 modified
src/lower.rs +112 -33 | @@ -27,8 +27,14 @@ const NIM_KEYWORDS: &[&str] = &[ | ||
| 27 | 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 | 36 | fn ident(name: &str) -> String { |
| 31 | - if NIM_KEYWORDS.contains(&name) { | |
| 37 | + if NIM_KEYWORDS.contains(&name) || NIM_SHADOWED.contains(&name) { | |
| 32 | 38 | return format!("{name}_r"); |
| 33 | 39 | } |
| 34 | 40 | // Nim identifiers may not begin with an underscore, and may not contain |
| @@ -308,6 +314,9 @@ pub struct Lowerer { | ||
| 308 | 314 | /// turn into an error. |
| 309 | 315 | local_items: HashMap<String, String>, |
| 310 | 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 | 320 | /// Module names supplied as separate input files. A `mod x;` naming one |
| 312 | 321 | /// of these is satisfied by that file having been passed in. |
| 313 | 322 | pub modules: Vec<String>, |
| @@ -381,6 +390,7 @@ impl Lowerer { | ||
| 381 | 390 | uses_i128: std::cell::Cell::new(false), |
| 382 | 391 | local_items: HashMap::new(), |
| 383 | 392 | emitted_local: std::collections::HashSet::new(), |
| 393 | + const_gens: HashMap::new(), | |
| 384 | 394 | modules: Vec::new(), |
| 385 | 395 | emitted: 0, |
| 386 | 396 | features: Vec::new(), |
| @@ -644,6 +654,7 @@ impl Lowerer { | ||
| 644 | 654 | // A body may declare its own types, and a signature below may |
| 645 | 655 | // name one, so these come first. |
| 646 | 656 | self.collect_body_items(&f.block)?; |
| 657 | + self.note_const_generics(&f.sig.generics)?; | |
| 647 | 658 | let (params, ret) = self.signature(&f.sig)?; |
| 648 | 659 | if matches!(ret, Nim::Future(_)) { |
| 649 | 660 | self.uses_async = true; |
| @@ -657,13 +668,7 @@ impl Lowerer { | ||
| 657 | 668 | .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() }); |
| 658 | 669 | } |
| 659 | 670 | 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)?; | |
| 667 | 672 | let g = Self::generics_of(&s.generics); |
| 668 | 673 | // The parameters must be in scope while the field types are |
| 669 | 674 | // mapped, so that `T` resolves to itself rather than to an |
| @@ -738,12 +743,7 @@ impl Lowerer { | ||
| 738 | 743 | } |
| 739 | 744 | Item::Enum(e) => { |
| 740 | 745 | 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)?; | |
| 747 | 747 | self.type_generics |
| 748 | 748 | .insert(name.clone(), Self::generics_of(&e.generics)); |
| 749 | 749 | |
| @@ -828,9 +828,11 @@ impl Lowerer { | ||
| 828 | 828 | fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> { |
| 829 | 829 | // Before anything else: a method body may declare its own types, and |
| 830 | 830 | // the trait branches below return early. |
| 831 | + self.note_const_generics(&im.generics)?; | |
| 831 | 832 | for it in &im.items { |
| 832 | 833 | if let syn::ImplItem::Fn(m) = it { |
| 833 | 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 | 1190 | .iter() |
| 1189 | 1191 | .filter_map(|p| match p { |
| 1190 | 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 | 1197 | _ => None, |
| 1192 | 1198 | }) |
| 1193 | 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 | 1229 | /// Bind a signature's type parameters by matching its declared parameter |
| 1197 | 1230 | /// types against the actual argument types, then substitute into `ret`. |
| 1198 | 1231 | /// |
| @@ -1317,12 +1350,54 @@ impl Lowerer { | ||
| 1317 | 1350 | } |
| 1318 | 1351 | |
| 1319 | 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 | 1390 | if params.is_empty() { |
| 1322 | - String::new() | |
| 1323 | - } else { | |
| 1324 | - format!("[{}]", params.join(", ")) | |
| 1391 | + return String::new(); | |
| 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 | 1403 | /// The Nim name for a function, qualified by its module. |
| @@ -1377,7 +1452,7 @@ impl Lowerer { | ||
| 1377 | 1452 | params.push(g); |
| 1378 | 1453 | } |
| 1379 | 1454 | } |
| 1380 | - let gens = Self::gen_list(¶ms); | |
| 1455 | + let gens = self.gen_list(¶ms); | |
| 1381 | 1456 | let mut parts = Vec::new(); |
| 1382 | 1457 | if let Some(self_ty) = recv { |
| 1383 | 1458 | let mutable = matches!( |
| @@ -1517,15 +1592,9 @@ impl Lowerer { | ||
| 1517 | 1592 | // `unsafe fn` marks a contract for callers; it does not change what |
| 1518 | 1593 | // the body means, so it lowers like any other proc. |
| 1519 | 1594 | // 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. | |
| 1529 | 1598 | let mut params = Vec::new(); |
| 1530 | 1599 | let mut fresh = 0usize; |
| 1531 | 1600 | for a in &sig.inputs { |
| @@ -1647,7 +1716,7 @@ impl Lowerer { | ||
| 1647 | 1716 | Item::Struct(s) => { |
| 1648 | 1717 | let name = s.ident.to_string(); |
| 1649 | 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 | 1720 | self.line(&format!("type {}*{} = object", ident(&name), g)); |
| 1652 | 1721 | self.indent += 1; |
| 1653 | 1722 | if fields.is_empty() { |
| @@ -2159,7 +2228,7 @@ impl Lowerer { | ||
| 2159 | 2228 | |
| 2160 | 2229 | fn emit_enum(&mut self, def: &EnumDef) { |
| 2161 | 2230 | let name = ident(&def.name); |
| 2162 | - let g = Self::gen_list( | |
| 2231 | + let g = self.gen_list( | |
| 2163 | 2232 | self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]), |
| 2164 | 2233 | ); |
| 2165 | 2234 | if def.simple && !g.is_empty() { |
| @@ -2489,7 +2558,7 @@ impl Lowerer { | ||
| 2489 | 2558 | gparams.push(g); |
| 2490 | 2559 | } |
| 2491 | 2560 | } |
| 2492 | - let gens = Self::gen_list(&gparams); | |
| 2561 | + let gens = self.gen_list(&gparams); | |
| 2493 | 2562 | let outer_fg = std::mem::replace(&mut self.fn_generics, gparams.clone()); |
| 2494 | 2563 | let head = if ret == Nim::Unit { |
| 2495 | 2564 | format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", ")) |
| @@ -4492,7 +4561,9 @@ impl Lowerer { | ||
| 4492 | 4561 | Val::new(format!("[{}]", elems.join(", ")), t) |
| 4493 | 4562 | } |
| 4494 | 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 | 4567 | t, |
| 4497 | 4568 | ), |
| 4498 | 4569 | }); |
| @@ -5623,7 +5694,15 @@ impl Lowerer { | ||
| 5623 | 5694 | .get(&(q.clone(), name.clone())) |
| 5624 | 5695 | .cloned() |
| 5625 | 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 | 5708 | 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 contain | 40 | // 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 one | 320 | /// 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 may | 654 | // 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 are | 673 | // The parameters must be in scope while the field types are |
| 669 | // mapped, so that `T` resolves to itself rather than to an | 674 | // 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_generics | 747 | 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, and | 829 | // 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 parameter | 1229 | /// 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(¶ms); | 1455 | + let gens = self.gen_list(¶ms); |
| 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 what | 1592 | // `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, so | 1594 | // 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] = | ||
| 317 | 317 | ## `[v; N]` where `N` is a const expression rather than a literal. Nim's |
| 318 | 318 | ## array literal has to be written out element by element, which needs the |
| 319 | 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 | |
| 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's | 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 | 318 | ## 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] = v | 321 | + 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> { | ||
| 286 | 286 | .iter() |
| 287 | 287 | .filter_map(|g| match g { |
| 288 | 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 | 293 | _ => None, |
| 290 | 294 | }) |
| 291 | 295 | .collect::<Result<_, _>>()?, |
| @@ -354,15 +358,7 @@ pub fn map(t: &Type) -> Result<Nim, String> { | ||
| 354 | 358 | } |
| 355 | 359 | Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), |
| 356 | 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 | 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 | 362 | Ok(Nim::Array(len, Box::new(map(&a.elem)?))) |
| 367 | 363 | } |
| 368 | 364 | 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 | +} | ||