nandi/rustnimpublic Fork 0
ff34e1b
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.

Add generics; transpile the part of cosmic-theme that is reachable

Rust type parameters become Nim's. Nim instantiates a generic structurally
at the call site much as Rust does, so no monomorphisation pass is needed:
fn f<T>(x: T) -> T maps to proc f[T](x: T): T.

Trait bounds and where clauses are dropped, which is sound in the direction
that matters. An operation a bound permitted either exists for the
instantiated type or is a compile error at that instantiation site, so
dropping it cannot change what an accepted program means -- it only lets
rustnim accept programs rustc would reject, which is irrelevant for
known-good input. Const generic parameters are still rejected.

Two things needed more than a rename. Nim cannot infer an object's generic
parameters from a constructor's field values, so Pair { a: 1, b: 2 } is
emitted as Pair[int32](..) from the expected type, and a generic enum's unit
variant likewise. And a binding cannot be annotated with a parameter Nim is
still inferring, so call sites now run a small unifier: the callee's declared
parameter types are matched against the actual argument types to bind T,
which is then substituted into the return type.

Also saturating_* and checked_* (open item 6), detecting overflow on the
unsigned view of the same width rather than with a range check that would
itself trap; i32::MAX and friends; From impls called as Type::from(x),
resolved by the argument's type since Nim cannot overload on return type; and
Self in struct literals.

cosmic-theme: corner.rs, spacing.rs and layout.rs transpile with output
byte-identical to rustc's, including the Density/Spacing and
Roundness/CornerRadii round trips. That is the spacing scale, corner radii
and density model a COSMIC-native UI needs to match the desktop. The files
are the crate's own with one mechanical change, recorded in DESIGN.md: the
serde import and two derive entries removed, because the oracle is plain
rustc with no dependencies. The rest of cosmic-theme is colour work on
palette -- 40,874 lines across 122 files plus a proc-macro crate -- and
mode.rs needs cosmic-config and its derive. Dependency walls, not language
gaps.

The survey result is worth more than the feature. Generics cleared 90 of 91
blockers across 400 crates and moved the number of fully-working crates from
2 to 2. Host cfg did the same thing last time: 90 of 124 cleared, zero net.
Every crate each unblocked simply hit its next blocker. Blockers are deep,
not wide, and a frequency ranking of first blockers is not a roadmap.

38 differential cases, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-18T21:56:34-07:00 Browse files
ff34e1b parent: 0e6c394
modified DESIGN.md +75 -26
@@ -214,6 +214,31 @@ items are emitted as `<module>_<name>`, and a call resolves through an explicit
214214 qualifier, then the current module, then what `use` brought into scope, then
215215 the root.
216216
217+### Generics
218+
219+Rust type parameters become Nim's. Nim instantiates a generic structurally at
220+the call site much as Rust does, so `fn f<T>(x: T) -> T` has a direct target in
221+`proc f[T](x: T): T` and no monomorphisation pass is needed.
222+
223+**Trait bounds and `where` clauses are dropped.** That is sound in the
224+direction that matters: an operation the bound permitted either exists for the
225+instantiated type or is a compile error at that instantiation site. Dropping a
226+bound cannot make an accepted program mean something different — it only makes
227+rustnim accept some programs rustc would reject, which does not matter when
228+the input is known-good Rust. (Where it *would* matter is bound-directed
229+method selection, e.g. blanket impls choosing between candidates. We do not
230+model trait resolution at all, so such a program is rejected elsewhere.)
231+
232+Const generic parameters have no Nim equivalent and are still rejected.
233+
234+Two things need more than a rename. Nim cannot infer an object's generic
235+parameters from a constructor's field values, so `Pair { a: 1, b: 2 }` is
236+emitted as `Pair[int32](...)` using the expected type — and a generic enum's
237+unit variant (`Holder::Empty`) likewise. And a binding's annotation cannot
238+name a parameter Nim is still inferring, so call sites run a small unifier:
239+the callee's declared parameter types are matched against the actual argument
240+types to bind `T`, and the result is substituted into the return type.
241+
217242 ### Declaration order
218243
219244 Rust has no declaration-before-use rule and Nim does, so every proc is
@@ -278,13 +303,14 @@ runner) rather than a wrong answer.
278303
279304 ### Still open
280305
281-6. `checked_*` and `saturating_*` are not mapped yet; they are currently
282- rejected as unsupported methods rather than approximated.
283-7. Generics (type and const parameters), `move` closures, closure bodies with
284- statements, and trait impls other than the formatting traits and `From` are
285- rejected with a reason.
306+6. Const generic parameters, `move` closures, closure bodies with statements,
307+ associated types in an `impl`, and trait objects are rejected with a reason.
286308 Lifetime parameters are *not* a rejection: they carry no runtime meaning
287309 and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.
310+7. `saturating_*` and `checked_*` are implemented, detecting overflow on the
311+ unsigned view of the same width rather than with a range check that would
312+ itself trap. `wrapping_*`, `overflowing_*` and `strict_*` are not all
313+ covered: only add, sub and mul have the saturating and checked forms.
288314 8. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but
289315 the exponent-form thresholds have only been checked at `1e21`.
290316 9. Functions are scoped by module now, but *types* are still global: two
@@ -364,33 +390,56 @@ is a north star, not a next step.
364390 400 crates from the local registry (under 4,000 lines each), all their module
365391 files passed together, first blocker recorded:
366392
367-| blocker | before | after host-`cfg` |
368-|---|---|---|
369-| unevaluable `#[cfg]` | 124 | 34 |
370-| generic type parameter | 69 | 91 |
371-| unsupported item in an `impl` (associated types) | 54 | 63 |
372-| trait object | 17 | 25 |
373-| macro definition | 21 | 24 |
374-| raw pointer | 12 | 22 |
375-| **crates fully transpiled** | **2** | **2** |
376-
377-Evaluating the host `#[cfg]` predicates cleared 90 of the 124 top blockers
378-**and moved the fully-working count by zero**. Every crate it unblocked
379-simply hit its next blocker. That is the shape of the problem: blockers are
380-*deep*, not wide. `base16ct` and `adler2` work because their whole stack was
381-ground through, not because any single feature was added.
393+| blocker | start | after host-`cfg` | after generics |
394+|---|---|---|---|
395+| unevaluable `#[cfg]` | 124 | 34 | 34 |
396+| generic type parameter | 69 | 91 | **1** |
397+| unsupported item in an `impl` (associated types) | 54 | 63 | 87 |
398+| unsupported type | 20 | 29 | 60 |
399+| trait object | 17 | 25 | 30 |
400+| macro definition | 21 | 24 | 25 |
401+| raw pointer | 12 | 22 | 24 |
402+| **crates fully transpiled** | **2** | **2** | **2** |
403+
404+This has now happened twice. Evaluating the host `#[cfg]` predicates cleared
405+90 of 124 blockers and moved the fully-working count by zero. Generics then
406+cleared 90 of 91 and moved it by zero again. Every crate each unblocked simply
407+hit its next blocker.
408+
409+That is the shape of the problem: blockers are **deep, not wide**. A frequency
410+ranking of *first* blockers is not a roadmap — it says which feature is most
411+often first, not which one finishes a crate. `base16ct`, `adler2` and
412+`cosmic-theme`'s spacing model work because their whole stack was ground
413+through, one blocker at a time.
382414
383415 So the ranking above is not a roadmap — it says which feature is most often
384416 *first*, which is not the same as which feature finishes a crate. The only
385417 honest way to add a crate is to pick it and clear its stack, as was done
386418 twice.
387419
388-The next rung, if one is wanted, is **generics**: it is now the top blocker,
389-it is what stops even `cosmic-theme`'s 4,234 lines of colour data, and Nim has
390-native generics, so `fn f<T>(x: T) -> T` has a real target in
391-`proc f[T](x: T): T` rather than needing monomorphisation. Associated types
392-(`type Item = ..` inside an `impl`) are the next after that and are mostly a
393-matter of recording a type binding.
420+Generics are now done. The next blocker by frequency is associated types
421+(`type Item = ..` inside an `impl`, 87), then unsupported types (60) and trait
422+objects (30) — but see the paragraph above before treating that as a plan.
423+
424+### `cosmic-theme`: what was reachable
425+
426+`tests/cases/032-cosmic-theme-spacing/` transpiles `corner.rs`, `spacing.rs`
427+and `layout.rs` from `cosmic-theme` 1.0.0 — the spacing scale, corner radii
428+and density model a COSMIC-native UI needs to match the desktop — with output
429+byte-identical to rustc's, including the `Density`/`Spacing` and
430+`Roundness`/`CornerRadii` round trips.
431+
432+Those files are the crate's own, with one mechanical change recorded here: the
433+`use serde::{Deserialize, Serialize}` line and the `Serialize, Deserialize`
434+entries in two `derive` lists were removed, because the oracle is plain
435+`rustc` with no dependencies available. Nothing else was touched; rustnim
436+ignores both anyway.
437+
438+The rest of `cosmic-theme` — `theme.rs` (1,830 lines), `color.rs`,
439+`cosmic_palette.rs`, `derivation.rs`, `steps.rs`, `composite.rs` — is colour
440+work built on `palette` (40,874 lines across 122 files, plus a proc-macro
441+crate). `mode.rs` needs `cosmic-config` and its derive macro. Those are
442+dependency walls, not language gaps.
394443
395444 ## Proof of byte-identity for `base16ct`
396445
@@ -214,6 +214,31 @@ items are emitted as `<module>_<name>`, and a call resolves through an explicit
214 qualifier, then the current module, then what `use` brought into scope, then214 qualifier, then the current module, then what `use` brought into scope, then
215 the root.215 the root.
216 216
217+### Generics
218+
219+Rust type parameters become Nim's. Nim instantiates a generic structurally at
220+the call site much as Rust does, so `fn f<T>(x: T) -> T` has a direct target in
221+`proc f[T](x: T): T` and no monomorphisation pass is needed.
222+
223+**Trait bounds and `where` clauses are dropped.** That is sound in the
224+direction that matters: an operation the bound permitted either exists for the
225+instantiated type or is a compile error at that instantiation site. Dropping a
226+bound cannot make an accepted program mean something different — it only makes
227+rustnim accept some programs rustc would reject, which does not matter when
228+the input is known-good Rust. (Where it *would* matter is bound-directed
229+method selection, e.g. blanket impls choosing between candidates. We do not
230+model trait resolution at all, so such a program is rejected elsewhere.)
231+
232+Const generic parameters have no Nim equivalent and are still rejected.
233+
234+Two things need more than a rename. Nim cannot infer an object's generic
235+parameters from a constructor's field values, so `Pair { a: 1, b: 2 }` is
236+emitted as `Pair[int32](...)` using the expected type — and a generic enum's
237+unit variant (`Holder::Empty`) likewise. And a binding's annotation cannot
238+name a parameter Nim is still inferring, so call sites run a small unifier:
239+the callee's declared parameter types are matched against the actual argument
240+types to bind `T`, and the result is substituted into the return type.
241+
217 ### Declaration order242 ### Declaration order
218 243
219 Rust has no declaration-before-use rule and Nim does, so every proc is244 Rust has no declaration-before-use rule and Nim does, so every proc is
@@ -278,13 +303,14 @@ runner) rather than a wrong answer.
278 303
279 ### Still open304 ### Still open
280 305
281-6. `checked_*` and `saturating_*` are not mapped yet; they are currently306+6. Const generic parameters, `move` closures, closure bodies with statements,
282- rejected as unsupported methods rather than approximated.307+ associated types in an `impl`, and trait objects are rejected with a reason.
283-7. Generics (type and const parameters), `move` closures, closure bodies with
284- statements, and trait impls other than the formatting traits and `From` are
285- rejected with a reason.
286 Lifetime parameters are *not* a rejection: they carry no runtime meaning308 Lifetime parameters are *not* a rejection: they carry no runtime meaning
287 and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.309 and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.
310+7. `saturating_*` and `checked_*` are implemented, detecting overflow on the
311+ unsigned view of the same width rather than with a range check that would
312+ itself trap. `wrapping_*`, `overflowing_*` and `strict_*` are not all
313+ covered: only add, sub and mul have the saturating and checked forms.
288 8. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but314 8. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but
289 the exponent-form thresholds have only been checked at `1e21`.315 the exponent-form thresholds have only been checked at `1e21`.
290 9. Functions are scoped by module now, but *types* are still global: two316 9. Functions are scoped by module now, but *types* are still global: two
@@ -364,33 +390,56 @@ is a north star, not a next step.
364 400 crates from the local registry (under 4,000 lines each), all their module390 400 crates from the local registry (under 4,000 lines each), all their module
365 files passed together, first blocker recorded:391 files passed together, first blocker recorded:
366 392
367-| blocker | before | after host-`cfg` |393+| blocker | start | after host-`cfg` | after generics |
368-|---|---|---|394+|---|---|---|---|
369-| unevaluable `#[cfg]` | 124 | 34 |395+| unevaluable `#[cfg]` | 124 | 34 | 34 |
370-| generic type parameter | 69 | 91 |396+| generic type parameter | 69 | 91 | **1** |
371-| unsupported item in an `impl` (associated types) | 54 | 63 |397+| unsupported item in an `impl` (associated types) | 54 | 63 | 87 |
372-| trait object | 17 | 25 |398+| unsupported type | 20 | 29 | 60 |
373-| macro definition | 21 | 24 |399+| trait object | 17 | 25 | 30 |
374-| raw pointer | 12 | 22 |400+| macro definition | 21 | 24 | 25 |
375-| **crates fully transpiled** | **2** | **2** |401+| raw pointer | 12 | 22 | 24 |
376-402+| **crates fully transpiled** | **2** | **2** | **2** |
377-Evaluating the host `#[cfg]` predicates cleared 90 of the 124 top blockers403+
378-**and moved the fully-working count by zero**. Every crate it unblocked404+This has now happened twice. Evaluating the host `#[cfg]` predicates cleared
379-simply hit its next blocker. That is the shape of the problem: blockers are405+90 of 124 blockers and moved the fully-working count by zero. Generics then
380-*deep*, not wide. `base16ct` and `adler2` work because their whole stack was406+cleared 90 of 91 and moved it by zero again. Every crate each unblocked simply
381-ground through, not because any single feature was added.407+hit its next blocker.
408+
409+That is the shape of the problem: blockers are **deep, not wide**. A frequency
410+ranking of *first* blockers is not a roadmap — it says which feature is most
411+often first, not which one finishes a crate. `base16ct`, `adler2` and
412+`cosmic-theme`'s spacing model work because their whole stack was ground
413+through, one blocker at a time.
382 414
383 So the ranking above is not a roadmap — it says which feature is most often415 So the ranking above is not a roadmap — it says which feature is most often
384 *first*, which is not the same as which feature finishes a crate. The only416 *first*, which is not the same as which feature finishes a crate. The only
385 honest way to add a crate is to pick it and clear its stack, as was done417 honest way to add a crate is to pick it and clear its stack, as was done
386 twice.418 twice.
387 419
388-The next rung, if one is wanted, is **generics**: it is now the top blocker,420+Generics are now done. The next blocker by frequency is associated types
389-it is what stops even `cosmic-theme`'s 4,234 lines of colour data, and Nim has421+(`type Item = ..` inside an `impl`, 87), then unsupported types (60) and trait
390-native generics, so `fn f<T>(x: T) -> T` has a real target in422+objects (30) — but see the paragraph above before treating that as a plan.
391-`proc f[T](x: T): T` rather than needing monomorphisation. Associated types423+
392-(`type Item = ..` inside an `impl`) are the next after that and are mostly a424+### `cosmic-theme`: what was reachable
393-matter of recording a type binding.425+
426+`tests/cases/032-cosmic-theme-spacing/` transpiles `corner.rs`, `spacing.rs`
427+and `layout.rs` from `cosmic-theme` 1.0.0 — the spacing scale, corner radii
428+and density model a COSMIC-native UI needs to match the desktop — with output
429+byte-identical to rustc's, including the `Density`/`Spacing` and
430+`Roundness`/`CornerRadii` round trips.
431+
432+Those files are the crate's own, with one mechanical change recorded here: the
433+`use serde::{Deserialize, Serialize}` line and the `Serialize, Deserialize`
434+entries in two `derive` lists were removed, because the oracle is plain
435+`rustc` with no dependencies available. Nothing else was touched; rustnim
436+ignores both anyway.
437+
438+The rest of `cosmic-theme` — `theme.rs` (1,830 lines), `color.rs`,
439+`cosmic_palette.rs`, `derivation.rs`, `steps.rs`, `composite.rs` — is colour
440+work built on `palette` (40,874 lines across 122 files, plus a proc-macro
441+crate). `mode.rs` needs `cosmic-config` and its derive macro. Those are
442+dependency walls, not language gaps.
394 443
395 ## Proof of byte-identity for `base16ct`444 ## Proof of byte-identity for `base16ct`
396 445
modified README.md +10 -4
@@ -30,7 +30,8 @@ rustnim src/lib.rs src/error.rs --cfg feature=alloc -o crate.nim
3030 ## What works
3131
3232 Functions and `impl` methods, structs, enums (C-like and data-carrying),
33-`Option`/`Result` with `?`, `let`/`let mut`, the integer and float operators at
33+`Option`/`Result` with `?`, generics, `saturating_*`/`checked_*`,
34+`let`/`let mut`, the integer and float operators at
3435 exact widths, `as` casts, `if`/`while`/`loop`/`for`, `match` including binding
3536 patterns, `Vec`/slices/arrays, type aliases, function-typed parameters
3637 (`impl Fn(A) -> B`), multi-file input, `#[cfg]`, and `println!`/`format!` with
@@ -85,9 +86,14 @@ byte-identical across every single byte, every length to 600, and 144
8586 incremental-write splits. It needed ten new features, and it caught a
8687 regression that 33 passing cases had not.
8788
88-Of five crates tried, one works, two are rejected for `u128` (the founding
89-rule working as designed), and two need features that are genuinely missing.
90-[`DESIGN.md`](DESIGN.md) has the table.
89+`cosmic-theme`'s spacing scale, corner radii and density model also transpile
90+byte-identically — the part of it that is not built on `palette`.
91+
92+A 400-crate survey says something worth knowing: clearing the single most
93+common blocker has twice moved the number of fully-working crates by *zero*,
94+because each unblocked crate just hits its next one. Blockers are deep, not
95+wide, and a frequency ranking of first blockers is not a roadmap.
96+[`DESIGN.md`](DESIGN.md) has the tables.
9197
9298 ## Tests
9399
@@ -30,7 +30,8 @@ rustnim src/lib.rs src/error.rs --cfg feature=alloc -o crate.nim
30 ## What works30 ## What works
31 31
32 Functions and `impl` methods, structs, enums (C-like and data-carrying),32 Functions and `impl` methods, structs, enums (C-like and data-carrying),
33-`Option`/`Result` with `?`, `let`/`let mut`, the integer and float operators at33+`Option`/`Result` with `?`, generics, `saturating_*`/`checked_*`,
34+`let`/`let mut`, the integer and float operators at
34 exact widths, `as` casts, `if`/`while`/`loop`/`for`, `match` including binding35 exact widths, `as` casts, `if`/`while`/`loop`/`for`, `match` including binding
35 patterns, `Vec`/slices/arrays, type aliases, function-typed parameters36 patterns, `Vec`/slices/arrays, type aliases, function-typed parameters
36 (`impl Fn(A) -> B`), multi-file input, `#[cfg]`, and `println!`/`format!` with37 (`impl Fn(A) -> B`), multi-file input, `#[cfg]`, and `println!`/`format!` with
@@ -85,9 +86,14 @@ byte-identical across every single byte, every length to 600, and 144
85 incremental-write splits. It needed ten new features, and it caught a86 incremental-write splits. It needed ten new features, and it caught a
86 regression that 33 passing cases had not.87 regression that 33 passing cases had not.
87 88
88-Of five crates tried, one works, two are rejected for `u128` (the founding89+`cosmic-theme`'s spacing scale, corner radii and density model also transpile
89-rule working as designed), and two need features that are genuinely missing.90+byte-identically — the part of it that is not built on `palette`.
90-[`DESIGN.md`](DESIGN.md) has the table.91+
92+A 400-crate survey says something worth knowing: clearing the single most
93+common blocker has twice moved the number of fully-working crates by *zero*,
94+because each unblocked crate just hits its next one. Blockers are deep, not
95+wide, and a frequency ranking of first blockers is not a roadmap.
96+[`DESIGN.md`](DESIGN.md) has the tables.
91 97
92 ## Tests98 ## Tests
93 99
modified src/lower.rs +380 -39
@@ -150,6 +150,9 @@ impl Val {
150150 struct Sig {
151151 params: Vec<Nim>,
152152 ret: Nim,
153+ /// Type parameters this signature is generic in, so a call site can bind
154+ /// them from its argument types.
155+ generics: Vec<String>,
153156 }
154157
155158 /// One variant of a Rust enum.
@@ -198,6 +201,12 @@ pub struct Lowerer {
198201 cur_mod: String,
199202 /// The type of the `impl` block being lowered, which `Self` names.
200203 self_ty: Option<Nim>,
204+ /// Type parameters of the enclosing `impl`, which its methods share.
205+ impl_generics: Vec<String>,
206+ /// Type parameters of the proc being lowered, impl's included.
207+ fn_generics: Vec<String>,
208+ /// Type parameters declared by each generic struct or enum.
209+ type_generics: HashMap<String, Vec<String>>,
201210 /// `use` brings a name into scope from another module. Flattening loses
202211 /// the module structure, so the mapping is recorded and consulted when a
203212 /// bare call is resolved.
@@ -268,6 +277,9 @@ impl Lowerer {
268277 fns: HashMap::new(),
269278 cur_mod: String::new(),
270279 self_ty: None,
280+ impl_generics: Vec::new(),
281+ fn_generics: Vec::new(),
282+ type_generics: HashMap::new(),
271283 use_map: HashMap::new(),
272284 structs: HashMap::new(),
273285 enums: HashMap::new(),
@@ -499,13 +511,26 @@ impl Lowerer {
499511 match item {
500512 Item::Fn(f) => {
501513 let (params, ret) = self.signature(&f.sig)?;
514+ let gen_names = Self::generics_of(&f.sig.generics);
502515 let name = f.sig.ident.to_string();
503516 let nim = self.fn_name(&self.cur_mod, &name);
504517 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
505518 self.fns
506- .insert((self.cur_mod.clone(), name), Sig { params, ret });
519+ .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() });
507520 }
508521 Item::Struct(s) => {
522+ if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
523+ return Err(format!(
524+ "`struct {}` has a const generic parameter, which Nim has \
525+ no equivalent for",
526+ s.ident
527+ ));
528+ }
529+ let g = Self::generics_of(&s.generics);
530+ // The parameters must be in scope while the field types are
531+ // mapped, so that `T` resolves to itself rather than to an
532+ // unknown named type.
533+ self.type_generics.insert(s.ident.to_string(), g);
509534 let mut fields = Vec::new();
510535 for (i, f) in s.fields.iter().enumerate() {
511536 let name = match &f.ident {
@@ -542,9 +567,14 @@ impl Lowerer {
542567 }
543568 Item::Enum(e) => {
544569 let name = e.ident.to_string();
545- if e.generics.params.iter().any(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
546- return Err(format!("`enum {name}` is generic: not implemented yet"));
570+ if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
571+ return Err(format!(
572+ "`enum {name}` has a const generic parameter, which Nim \
573+ has no equivalent for"
574+ ));
547575 }
576+ self.type_generics
577+ .insert(name.clone(), Self::generics_of(&e.generics));
548578 let mut variants = Vec::new();
549579 for v in &e.variants {
550580 let vname = v.ident.to_string();
@@ -582,10 +612,13 @@ impl Lowerer {
582612 );
583613 }
584614 Item::Impl(im) => {
615+ let outer_g =
616+ std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics));
585617 let self_ty = self.map_ty(&im.self_ty)?;
586618 let outer_self = self.self_ty.replace(self_ty.clone());
587619 let r = self.collect_impl(im, &self_ty);
588620 self.self_ty = outer_self;
621+ self.impl_generics = outer_g;
589622 return r;
590623 }
591624 _ => {}
@@ -644,6 +677,8 @@ impl Lowerer {
644677 };
645678 let mname = m.sig.ident.to_string();
646679 let (mut params, ret) = self.signature(&m.sig)?;
680+ let mut gen_names = self.impl_generics.clone();
681+ gen_names.extend(Self::generics_of(&m.sig.generics));
647682 let recv = if takes_self(&m.sig) {
648683 params.insert(0, self_ty.clone());
649684 Some(self_ty.clone())
@@ -653,7 +688,7 @@ impl Lowerer {
653688 let nim = trait_method_name(&tyname, &tr, &mname);
654689 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
655690 self.methods
656- .insert((tyname.clone(), mname.clone()), Sig { params, ret });
691+ .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() });
657692 self.statics.insert((tyname.clone(), mname), nim);
658693 }
659694 return Ok(());
@@ -661,6 +696,8 @@ impl Lowerer {
661696 for it in &im.items {
662697 if let syn::ImplItem::Fn(m) = it {
663698 let (mut params, ret) = self.signature(&m.sig)?;
699+ let mut gen_names = self.impl_generics.clone();
700+ gen_names.extend(Self::generics_of(&m.sig.generics));
664701 if takes_self(&m.sig) {
665702 params.insert(0, self_ty.clone());
666703 }
@@ -669,7 +706,7 @@ impl Lowerer {
669706 let head = self.head_of(&nim, &m.sig, recv.as_ref())?;
670707 self.forwards.push(head);
671708 self.methods
672- .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret });
709+ .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret, generics: gen_names.clone() });
673710 self.statics
674711 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
675712 }
@@ -788,6 +825,22 @@ impl Lowerer {
788825 Ok(self.subst_self(n))
789826 }
790827
828+ /// Substitute a generic type's parameters with the arguments the use site
829+ /// supplies: a field of `Holder<T>` read through a `Holder<i32>` is `i32`.
830+ fn subst_type_args(&self, name: &str, used_as: &Nim, field: Nim) -> Nim {
831+ let Some(params) = self.type_generics.get(name) else { return field };
832+ if params.is_empty() {
833+ return field;
834+ }
835+ let Nim::Named(n, args) = used_as else { return field };
836+ if n != name || args.len() != params.len() {
837+ return field;
838+ }
839+ let map: HashMap<String, Nim> =
840+ params.iter().cloned().zip(args.iter().cloned()).collect();
841+ Self::subst(&field, &map)
842+ }
843+
791844 /// `Self` inside an `impl` block names the type being implemented.
792845 fn subst_self(&self, t: Nim) -> Nim {
793846 let Some(me) = &self.self_ty else { return t };
@@ -841,6 +894,115 @@ impl Lowerer {
841894 self.expand(&substitute(target, params, &args), depth + 1)
842895 }
843896
897+ /// The type parameters a generic item declares.
898+ ///
899+ /// Trait bounds and `where` clauses are dropped. Nim instantiates a
900+ /// generic structurally: an operation the bound would have permitted
901+ /// either exists for the instantiated type or is a compile error at the
902+ /// instantiation site. So dropping a bound cannot make an accepted
903+ /// program mean something different — it only makes rustnim accept some
904+ /// programs rustc would have rejected, which does not matter when the
905+ /// input is known-good Rust.
906+ fn generics_of(g: &syn::Generics) -> Vec<String> {
907+ g.params
908+ .iter()
909+ .filter_map(|p| match p {
910+ syn::GenericParam::Type(t) => Some(t.ident.to_string()),
911+ _ => None,
912+ })
913+ .collect()
914+ }
915+
916+ /// Bind a signature's type parameters by matching its declared parameter
917+ /// types against the actual argument types, then substitute into `ret`.
918+ ///
919+ /// This is the small amount of inference a call site needs: Nim will
920+ /// resolve the instantiation itself, but the *binding* still has to be
921+ /// annotated with a concrete type, and `T` is not one.
922+ fn instantiate(sig: &Sig, args: &[Option<Nim>]) -> Nim {
923+ if sig.generics.is_empty() {
924+ return sig.ret.clone();
925+ }
926+ let mut bound: HashMap<String, Nim> = HashMap::new();
927+ for (decl, actual) in sig.params.iter().zip(args) {
928+ if let Some(a) = actual {
929+ Self::unify(decl, a, &sig.generics, &mut bound);
930+ }
931+ }
932+ Self::subst(&sig.ret, &bound)
933+ }
934+
935+ fn unify(decl: &Nim, actual: &Nim, params: &[String], out: &mut HashMap<String, Nim>) {
936+ match (decl, actual) {
937+ (Nim::Named(n, da), _) if params.iter().any(|p| p == n) && da.is_empty() => {
938+ out.entry(n.clone()).or_insert_with(|| actual.clone());
939+ }
940+ (Nim::Named(_, da), Nim::Named(_, aa)) if da.len() == aa.len() => {
941+ for (d, a) in da.iter().zip(aa) {
942+ Self::unify(d, a, params, out);
943+ }
944+ }
945+ (Nim::Seq(d), Nim::Seq(a))
946+ | (Nim::OpenArray(d), Nim::OpenArray(a))
947+ | (Nim::Seq(d), Nim::OpenArray(a))
948+ | (Nim::OpenArray(d), Nim::Seq(a))
949+ | (Nim::Var(d), Nim::Var(a))
950+ | (Nim::Array(_, d), Nim::Array(_, a)) => Self::unify(d, a, params, out),
951+ (Nim::Var(d), a) => Self::unify(d, a, params, out),
952+ (d, Nim::Var(a)) => Self::unify(d, a, params, out),
953+ (Nim::Tuple(d), Nim::Tuple(a)) if d.len() == a.len() => {
954+ for (d, a) in d.iter().zip(a) {
955+ Self::unify(d, a, params, out);
956+ }
957+ }
958+ _ => {}
959+ }
960+ }
961+
962+ fn subst(t: &Nim, m: &HashMap<String, Nim>) -> Nim {
963+ match t {
964+ Nim::Named(n, a) if a.is_empty() => m.get(n).cloned().unwrap_or_else(|| t.clone()),
965+ Nim::Named(n, a) => {
966+ Nim::Named(n.clone(), a.iter().map(|x| Self::subst(x, m)).collect())
967+ }
968+ Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))),
969+ Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))),
970+ Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))),
971+ Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))),
972+ Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()),
973+ other => other.clone(),
974+ }
975+ }
976+
977+ /// Whether a type mentions a type parameter that is in scope here. Such a
978+ /// type cannot be used as a Nim annotation at an instantiation site: Nim
979+ /// infers it, and writing `T` would name something that is not bound.
980+ fn mentions_type_param(&self, t: &Nim) -> bool {
981+ match t {
982+ Nim::Named(n, a) => {
983+ self.fn_generics.iter().any(|g| g == n)
984+ || a.iter().any(|x| self.mentions_type_param(x))
985+ }
986+ Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) => {
987+ self.mentions_type_param(e)
988+ }
989+ Nim::Tuple(ts) => ts.iter().any(|x| self.mentions_type_param(x)),
990+ Nim::Proc(a, r) => {
991+ a.iter().any(|x| self.mentions_type_param(x)) || self.mentions_type_param(r)
992+ }
993+ _ => false,
994+ }
995+ }
996+
997+ /// `[T, U]`, or empty.
998+ fn gen_list(params: &[String]) -> String {
999+ if params.is_empty() {
1000+ String::new()
1001+ } else {
1002+ format!("[{}]", params.join(", "))
1003+ }
1004+ }
1005+
8441006 /// The Nim name for a function, qualified by its module.
8451007 fn fn_name(&self, module: &str, name: &str) -> String {
8461008 if module.is_empty() {
@@ -882,6 +1044,15 @@ impl Lowerer {
8821044 recv: Option<&Nim>,
8831045 ) -> Result<String, String> {
8841046 let (ptys, ret) = self.signature(sig)?;
1047+ // A method inside `impl<T> Foo<T>` is generic in the impl's
1048+ // parameters as well as its own.
1049+ let mut params = self.impl_generics.clone();
1050+ for g in Self::generics_of(&sig.generics) {
1051+ if !params.contains(&g) {
1052+ params.push(g);
1053+ }
1054+ }
1055+ let gens = Self::gen_list(&params);
8851056 let mut parts = Vec::new();
8861057 if let Some(self_ty) = recv {
8871058 let mutable = matches!(
@@ -910,9 +1081,15 @@ impl Lowerer {
9101081 parts.push(format!("{}: {}", ident(&pname), t.render()));
9111082 }
9121083 Ok(if ret == Nim::Unit {
913- format!("proc {}*({})", ident(name), parts.join(", "))
1084+ format!("proc {}*{}({})", ident(name), gens, parts.join(", "))
9141085 } else {
915- format!("proc {}*({}): {}", ident(name), parts.join(", "), ret.render())
1086+ format!(
1087+ "proc {}*{}({}): {}",
1088+ ident(name),
1089+ gens,
1090+ parts.join(", "),
1091+ ret.render()
1092+ )
9161093 })
9171094 }
9181095
@@ -923,15 +1100,12 @@ impl Lowerer {
9231100 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
9241101 }
9251102 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
926- // `fn encode<'a>(..)` is not generic for our purposes. Type and const
927- // parameters genuinely are, and are rejected.
928- if let Some(p) = sig.generics.params.iter().find(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
929- let what = match p {
930- syn::GenericParam::Const(_) => "const",
931- _ => "type",
932- };
1103+ // they disappear. Type parameters become Nim generic parameters.
1104+ // Const parameters have no Nim equivalent and are still rejected.
1105+ if sig.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
9331106 return Err(format!(
934- "`fn {}` has a {what} parameter: generics are not implemented yet",
1107+ "`fn {}` has a const generic parameter, which Nim has no \
1108+ equivalent for",
9351109 sig.ident
9361110 ));
9371111 }
@@ -997,7 +1171,8 @@ impl Lowerer {
9971171 Item::Struct(s) => {
9981172 let name = s.ident.to_string();
9991173 let fields = self.structs[&name].clone();
1000- self.line(&format!("type {}* = object", ident(&name)));
1174+ let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1175+ self.line(&format!("type {}*{} = object", ident(&name), g));
10011176 self.indent += 1;
10021177 if fields.is_empty() {
10031178 self.line("discard");
@@ -1038,10 +1213,13 @@ impl Lowerer {
10381213 Ok(())
10391214 }
10401215 Item::Impl(im) => {
1216+ let outer_g =
1217+ std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics));
10411218 let self_ty = self.map_ty(&im.self_ty)?;
10421219 let outer = self.self_ty.replace(self_ty.clone());
10431220 let r = self.impl_body(im, &self_ty);
10441221 self.self_ty = outer;
1222+ self.impl_generics = outer_g;
10451223 r
10461224 }
10471225 // `use` and `extern crate` are resolution directives with no Nim
@@ -1149,7 +1327,16 @@ impl Lowerer {
11491327
11501328 fn emit_enum(&mut self, def: &EnumDef) {
11511329 let name = ident(&def.name);
1152- if def.simple {
1330+ let g = Self::gen_list(
1331+ self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
1332+ );
1333+ if def.simple && !g.is_empty() {
1334+ // A Nim `enum` cannot take parameters; an all-unit generic enum
1335+ // has no payload to be generic in anyway, so this would be a
1336+ // parameter that never appears.
1337+ // Fall through to the object-variant form instead.
1338+ }
1339+ if def.simple && g.is_empty() {
11531340 // Every variant is a unit variant, so a plain Nim enum is an exact
11541341 // fit: it compares, orders and `case`-checks like Rust's.
11551342 self.line(&format!("type {name}* = enum"));
@@ -1182,7 +1369,7 @@ impl Lowerer {
11821369 }
11831370 self.indent -= 1;
11841371 self.blank();
1185- self.line(&format!("{}* = object", name));
1372+ self.line(&format!("{}*{} = object", name, g));
11861373 self.indent += 1;
11871374 self.line(&format!("case kind*: {}Kind", name));
11881375 for v in &def.variants {
@@ -1215,18 +1402,20 @@ impl Lowerer {
12151402 .collect();
12161403 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
12171404 all.extend(inits);
1405+ let ret = format!("{}{}", name, g);
12181406 self.line(&format!(
1219- "proc {}*({}): {} = {}({})",
1407+ "proc {}*{}({}): {} = {}({})",
12201408 def.ctor_ident(&v.name),
1409+ g,
12211410 args.join(", "),
1222- name,
1223- name,
1411+ ret,
1412+ ret,
12241413 all.join(", ")
12251414 ));
12261415 }
12271416 self.blank();
12281417
1229- self.line(&format!("proc rsDebug*(x: {name}): string ="));
1418+ self.line(&format!("proc rsDebug*{}(x: {}{}): string =", g, name, g));
12301419 self.indent += 1;
12311420 self.line("case x.kind");
12321421 for v in &def.variants {
@@ -1250,6 +1439,30 @@ impl Lowerer {
12501439 self.blank();
12511440 }
12521441
1442+ /// The concrete type an enum variant constructs, and the `[T]` list to
1443+ /// spell at the constructor when the enum is generic.
1444+ fn variant_type(
1445+ &self,
1446+ def: &EnumDef,
1447+ expect: Option<&Nim>,
1448+ ) -> Result<(Nim, String), String> {
1449+ let params = self.type_generics.get(&def.name).cloned().unwrap_or_default();
1450+ if params.is_empty() {
1451+ return Ok((Nim::Named(def.name.clone(), vec![]), String::new()));
1452+ }
1453+ match expect {
1454+ Some(Nim::Named(n, a)) if *n == def.name && a.len() == params.len() => Ok((
1455+ Nim::Named(def.name.clone(), a.clone()),
1456+ format!("[{}]", a.iter().map(|t| t.render()).collect::<Vec<_>>().join(", ")),
1457+ )),
1458+ _ => Err(format!(
1459+ "`{}` is a variant of a generic enum, and its type parameters \
1460+ cannot be inferred here; annotate the binding or the return type",
1461+ def.name
1462+ )),
1463+ }
1464+ }
1465+
12531466 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
12541467 /// to the enum that declares it.
12551468 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
@@ -1416,10 +1629,24 @@ impl Lowerer {
14161629 self.bind(&pname, t.clone().owned());
14171630 }
14181631
1632+ let mut gparams = self.impl_generics.clone();
1633+ for g in Self::generics_of(&sig.generics) {
1634+ if !gparams.contains(&g) {
1635+ gparams.push(g);
1636+ }
1637+ }
1638+ let gens = Self::gen_list(&gparams);
1639+ let outer_fg = std::mem::replace(&mut self.fn_generics, gparams.clone());
14191640 let head = if ret == Nim::Unit {
1420- format!("proc {}*({}) =", ident(name), rendered.join(", "))
1641+ format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
14211642 } else {
1422- format!("proc {}*({}): {} =", ident(name), rendered.join(", "), ret.render())
1643+ format!(
1644+ "proc {}*{}({}): {} =",
1645+ ident(name),
1646+ gens,
1647+ rendered.join(", "),
1648+ ret.render()
1649+ )
14231650 };
14241651 self.line(&head);
14251652 self.indent += 1;
@@ -1458,6 +1685,7 @@ impl Lowerer {
14581685
14591686 self.indent -= 1;
14601687 self.ret = outer_ret;
1688+ self.fn_generics = outer_fg;
14611689 self.pop_scope();
14621690 self.blank();
14631691 Ok(())
@@ -1669,7 +1897,13 @@ impl Lowerer {
16691897 // `var` parameter is wanted, so the binding has to be one.
16701898 let mutable = mutable || is_mut_borrow(&init.expr);
16711899 let kw = if mutable { "var" } else { "let" };
1672- let line = format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code);
1900+ // Inside a generic proc the binding's type may mention a parameter Nim
1901+ // will infer; naming it in an annotation would not resolve.
1902+ let line = if self.mentions_type_param(&t) {
1903+ format!("{} {} = {}", kw, ident(&name), v.code)
1904+ } else {
1905+ format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)
1906+ };
16731907 self.line(&line);
16741908 self.bind(&name, t);
16751909 Ok(())
@@ -2436,7 +2670,13 @@ impl Lowerer {
24362670 let Some((def, v)) = self.resolve_variant(path) else {
24372671 return Err(format!("`{last}` is not a known enum variant"));
24382672 };
2439- Ok(def.get(&v).map(|v| v.fields.clone()).unwrap_or_default())
2673+ let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2674+ // The variant's payload is declared in the enum's own parameters; the
2675+ // scrutinee says what they are here.
2676+ Ok(fields
2677+ .into_iter()
2678+ .map(|(n, ft)| (n, self.subst_type_args(&def.name, t, ft)))
2679+ .collect())
24402680 }
24412681
24422682 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
@@ -2529,6 +2769,21 @@ impl Lowerer {
25292769 if name == "None" {
25302770 return Ok(Val::new(self.none_of(expect), expect.cloned()));
25312771 }
2772+ // `i32::MAX` and friends: an associated const on a primitive.
2773+ if matches!(name.as_str(), "MAX" | "MIN") {
2774+ if let Some(q) = p.path.segments.iter().rev().nth(1) {
2775+ if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) {
2776+ if t.is_integer() {
2777+ let f = if name == "MAX" { "high" } else { "low" };
2778+ return Ok(Val::new(
2779+ format!("{}({})", f, t.render()),
2780+ Some(t),
2781+ ));
2782+ }
2783+ }
2784+ }
2785+ }
2786+
25322787 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
25332788 // declared here. In Nim that is a constructor call.
25342789 if p.path.segments.len() > 1 {
@@ -2547,11 +2802,16 @@ impl Lowerer {
25472802 }
25482803 // A unit enum variant used as a value: `Error::InvalidLength`.
25492804 if let Some((def, v)) = self.resolve_variant(&p.path) {
2550- let ty = Some(Nim::Named(def.name.clone(), vec![]));
2551- return Ok(if def.simple {
2552- Val::new(format!("{}.{}", ident(&def.name), ident(&v)), ty)
2805+ let (ty, targs) = self.variant_type(&def, expect)?;
2806+ return Ok(if def.simple && targs.is_empty() {
2807+ Val::new(format!("{}.{}", ident(&def.name), ident(&v)), Some(ty))
25532808 } else {
2554- Val::new(format!("{}()", def.ctor_ident(&v)), ty)
2809+ // A unit variant of a generic enum has no argument to
2810+ // infer the parameters from, so they are written out.
2811+ Val::new(
2812+ format!("{}{}()", def.ctor_ident(&v), targs),
2813+ Some(ty),
2814+ )
25552815 });
25562816 }
25572817 // A `for` binding that stands for an element of the container
@@ -2653,11 +2913,11 @@ impl Lowerer {
26532913 syn::Member::Unnamed(i) => format!("f{}", i.index),
26542914 };
26552915 let t = match &base.ty {
2656- Some(Nim::Named(s, _)) => self
2916+ Some(bt @ Nim::Named(s, _)) => self
26572917 .structs
26582918 .get(s)
26592919 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
2660- .map(|(_, t)| t.clone()),
2920+ .map(|(_, t)| self.subst_type_args(s, bt, t.clone())),
26612921 _ => None,
26622922 };
26632923 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
@@ -2732,7 +2992,16 @@ impl Lowerer {
27322992 Some(Nim::Named(def.name.clone(), vec![])),
27332993 ));
27342994 }
2735- let name = path_name(&s.path);
2995+ // `Self { .. }` inside an `impl` names the type being
2996+ // implemented, and its fields are that type's fields.
2997+ let name = match path_name(&s.path).as_str() {
2998+ "Self" => self
2999+ .self_ty
3000+ .as_ref()
3001+ .map(type_name)
3002+ .ok_or("`Self` outside an `impl` block")?,
3003+ other => other.to_string(),
3004+ };
27363005 let mut parts = Vec::new();
27373006 for f in &s.fields {
27383007 let fname = match &f.member {
@@ -2747,9 +3016,28 @@ impl Lowerer {
27473016 let v = self.expr_at(&f.expr, want.as_ref())?;
27483017 parts.push(format!("{}: {}", ident(&fname), v.code));
27493018 }
3019+ // Nim cannot infer an object's generic parameters from a
3020+ // constructor's field values, so they are written out.
3021+ let gp = self.type_generics.get(&name).cloned().unwrap_or_default();
3022+ let ty = if gp.is_empty() {
3023+ Nim::Named(name.clone(), vec![])
3024+ } else {
3025+ match expect {
3026+ Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => {
3027+ Nim::Named(name.clone(), a.clone())
3028+ }
3029+ _ => {
3030+ return Err(format!(
3031+ "`{name} {{ .. }}` is generic, and Nim cannot infer \
3032+ its parameters from the field values; annotate the \
3033+ binding or the return type"
3034+ ))
3035+ }
3036+ }
3037+ };
27503038 Ok(Val::new(
2751- format!("{}({})", ident(&name), parts.join(", ")),
2752- Some(Nim::Named(name, vec![])),
3039+ format!("{}({})", ty.render(), parts.join(", ")),
3040+ Some(ty),
27533041 ))
27543042 }
27553043 Expr::Array(a) => {
@@ -3446,6 +3734,27 @@ impl Lowerer {
34463734 }
34473735 }
34483736
3737+ // `Spacing::from(d)`: a `From` impl called through its target type.
3738+ // Rust picks the impl by the argument's type, and so do we -- Nim
3739+ // cannot overload on return type, so each impl has its own proc name.
3740+ if name == "from" && codes.len() == 1 {
3741+ if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3742+ let q = if q == "Self" {
3743+ self.self_ty.as_ref().map(type_name).unwrap_or(q)
3744+ } else {
3745+ q
3746+ };
3747+ if let Some(src) = args[0].ty.as_ref().map(type_name) {
3748+ if let Some(f) = self.from_impls.get(&(src, q.clone())).cloned() {
3749+ return Ok(Val::new(
3750+ format!("{}({})", f, codes[0]),
3751+ Some(Nim::Named(q, vec![])),
3752+ ));
3753+ }
3754+ }
3755+ }
3756+ }
3757+
34493758 // `u32::from(b)`: `From` between primitives is lossless by definition
34503759 // -- it is the widening direction only -- so a plain Nim conversion is
34513760 // exact. (The truncating direction is `as`, which is `cast`.)
@@ -3494,9 +3803,10 @@ impl Lowerer {
34943803
34953804 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
34963805 if let Some((def, v)) = self.resolve_variant(&p.path) {
3806+ let (ty, _) = self.variant_type(&def, expect)?;
34973807 return Ok(Val::new(
34983808 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
3499- Some(Nim::Named(def.name.clone(), vec![])),
3809+ Some(ty),
35003810 ));
35013811 }
35023812
@@ -3520,7 +3830,8 @@ impl Lowerer {
35203830 q
35213831 };
35223832 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
3523- let ret = sig.ret.clone();
3833+ let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
3834+ let ret = Self::instantiate(sig, &arg_tys);
35243835 let nim = self
35253836 .statics
35263837 .get(&(q.clone(), name.clone()))
@@ -3529,7 +3840,11 @@ impl Lowerer {
35293840 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
35303841 }
35313842 }
3532- let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone());
3843+ let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
3844+ let ret = target
3845+ .as_ref()
3846+ .and_then(|k| self.fns.get(k))
3847+ .map(|sig| Self::instantiate(sig, &arg_tys));
35333848 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
35343849 return Err(format!(
35353850 "call to unknown function `{name}`; only functions defined in \
@@ -3822,6 +4137,30 @@ impl Lowerer {
38224137 };
38234138 (format!("result.add({})", text), Some(Nim::Unit))
38244139 }
4140+ "saturating_add" | "saturating_sub" | "saturating_mul" | "checked_add"
4141+ | "checked_sub" | "checked_mul" => {
4142+ let t = rt
4143+ .clone()
4144+ .filter(|t| t.is_integer())
4145+ .ok_or_else(|| format!("`{name}` needs a known integer receiver"))?;
4146+ let arg = args
4147+ .first()
4148+ .ok_or_else(|| format!("`{name}` takes one argument"))?;
4149+ let f = match name.as_str() {
4150+ "saturating_add" => "rsSatAdd",
4151+ "saturating_sub" => "rsSatSub",
4152+ "saturating_mul" => "rsSatMul",
4153+ "checked_add" => "rsChkAdd",
4154+ "checked_sub" => "rsChkSub",
4155+ _ => "rsChkMul",
4156+ };
4157+ let out = if name.starts_with("checked") {
4158+ Nim::Named("Option".into(), vec![t])
4159+ } else {
4160+ t
4161+ };
4162+ (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
4163+ }
38254164 "abs" => (format!("abs({})", recv.code), rt.clone()),
38264165 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
38274166 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
@@ -3853,10 +4192,12 @@ impl Lowerer {
38534192 // A method defined in this file via `impl`, found by the
38544193 // receiver's type rather than by name alone.
38554194 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
4195+ let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
4196+ arg_tys.extend(args.iter().map(|a| a.ty.clone()));
38564197 let sig = key
38574198 .as_ref()
38584199 .and_then(|k| self.methods.get(k))
3859- .map(|s| s.ret.clone());
4200+ .map(|s| Self::instantiate(s, &arg_tys));
38604201 if let Some(ret) = sig {
38614202 // Use the name the proc was actually emitted under: an
38624203 // inherent method is qualified by its module, a trait
@@ -150,6 +150,9 @@ impl Val {
150 struct Sig {150 struct Sig {
151 params: Vec<Nim>,151 params: Vec<Nim>,
152 ret: Nim,152 ret: Nim,
153+ /// Type parameters this signature is generic in, so a call site can bind
154+ /// them from its argument types.
155+ generics: Vec<String>,
153 }156 }
154 157
155 /// One variant of a Rust enum.158 /// One variant of a Rust enum.
@@ -198,6 +201,12 @@ pub struct Lowerer {
198 cur_mod: String,201 cur_mod: String,
199 /// The type of the `impl` block being lowered, which `Self` names.202 /// The type of the `impl` block being lowered, which `Self` names.
200 self_ty: Option<Nim>,203 self_ty: Option<Nim>,
204+ /// Type parameters of the enclosing `impl`, which its methods share.
205+ impl_generics: Vec<String>,
206+ /// Type parameters of the proc being lowered, impl's included.
207+ fn_generics: Vec<String>,
208+ /// Type parameters declared by each generic struct or enum.
209+ type_generics: HashMap<String, Vec<String>>,
201 /// `use` brings a name into scope from another module. Flattening loses210 /// `use` brings a name into scope from another module. Flattening loses
202 /// the module structure, so the mapping is recorded and consulted when a211 /// the module structure, so the mapping is recorded and consulted when a
203 /// bare call is resolved.212 /// bare call is resolved.
@@ -268,6 +277,9 @@ impl Lowerer {
268 fns: HashMap::new(),277 fns: HashMap::new(),
269 cur_mod: String::new(),278 cur_mod: String::new(),
270 self_ty: None,279 self_ty: None,
280+ impl_generics: Vec::new(),
281+ fn_generics: Vec::new(),
282+ type_generics: HashMap::new(),
271 use_map: HashMap::new(),283 use_map: HashMap::new(),
272 structs: HashMap::new(),284 structs: HashMap::new(),
273 enums: HashMap::new(),285 enums: HashMap::new(),
@@ -499,13 +511,26 @@ impl Lowerer {
499 match item {511 match item {
500 Item::Fn(f) => {512 Item::Fn(f) => {
501 let (params, ret) = self.signature(&f.sig)?;513 let (params, ret) = self.signature(&f.sig)?;
514+ let gen_names = Self::generics_of(&f.sig.generics);
502 let name = f.sig.ident.to_string();515 let name = f.sig.ident.to_string();
503 let nim = self.fn_name(&self.cur_mod, &name);516 let nim = self.fn_name(&self.cur_mod, &name);
504 self.forwards.push(self.head_of(&nim, &f.sig, None)?);517 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
505 self.fns518 self.fns
506- .insert((self.cur_mod.clone(), name), Sig { params, ret });519+ .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() });
507 }520 }
508 Item::Struct(s) => {521 Item::Struct(s) => {
522+ if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
523+ return Err(format!(
524+ "`struct {}` has a const generic parameter, which Nim has \
525+ no equivalent for",
526+ s.ident
527+ ));
528+ }
529+ let g = Self::generics_of(&s.generics);
530+ // The parameters must be in scope while the field types are
531+ // mapped, so that `T` resolves to itself rather than to an
532+ // unknown named type.
533+ self.type_generics.insert(s.ident.to_string(), g);
509 let mut fields = Vec::new();534 let mut fields = Vec::new();
510 for (i, f) in s.fields.iter().enumerate() {535 for (i, f) in s.fields.iter().enumerate() {
511 let name = match &f.ident {536 let name = match &f.ident {
@@ -542,9 +567,14 @@ impl Lowerer {
542 }567 }
543 Item::Enum(e) => {568 Item::Enum(e) => {
544 let name = e.ident.to_string();569 let name = e.ident.to_string();
545- if e.generics.params.iter().any(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {570+ if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
546- return Err(format!("`enum {name}` is generic: not implemented yet"));571+ return Err(format!(
572+ "`enum {name}` has a const generic parameter, which Nim \
573+ has no equivalent for"
574+ ));
547 }575 }
576+ self.type_generics
577+ .insert(name.clone(), Self::generics_of(&e.generics));
548 let mut variants = Vec::new();578 let mut variants = Vec::new();
549 for v in &e.variants {579 for v in &e.variants {
550 let vname = v.ident.to_string();580 let vname = v.ident.to_string();
@@ -582,10 +612,13 @@ impl Lowerer {
582 );612 );
583 }613 }
584 Item::Impl(im) => {614 Item::Impl(im) => {
615+ let outer_g =
616+ std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics));
585 let self_ty = self.map_ty(&im.self_ty)?;617 let self_ty = self.map_ty(&im.self_ty)?;
586 let outer_self = self.self_ty.replace(self_ty.clone());618 let outer_self = self.self_ty.replace(self_ty.clone());
587 let r = self.collect_impl(im, &self_ty);619 let r = self.collect_impl(im, &self_ty);
588 self.self_ty = outer_self;620 self.self_ty = outer_self;
621+ self.impl_generics = outer_g;
589 return r;622 return r;
590 }623 }
591 _ => {}624 _ => {}
@@ -644,6 +677,8 @@ impl Lowerer {
644 };677 };
645 let mname = m.sig.ident.to_string();678 let mname = m.sig.ident.to_string();
646 let (mut params, ret) = self.signature(&m.sig)?;679 let (mut params, ret) = self.signature(&m.sig)?;
680+ let mut gen_names = self.impl_generics.clone();
681+ gen_names.extend(Self::generics_of(&m.sig.generics));
647 let recv = if takes_self(&m.sig) {682 let recv = if takes_self(&m.sig) {
648 params.insert(0, self_ty.clone());683 params.insert(0, self_ty.clone());
649 Some(self_ty.clone())684 Some(self_ty.clone())
@@ -653,7 +688,7 @@ impl Lowerer {
653 let nim = trait_method_name(&tyname, &tr, &mname);688 let nim = trait_method_name(&tyname, &tr, &mname);
654 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);689 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
655 self.methods690 self.methods
656- .insert((tyname.clone(), mname.clone()), Sig { params, ret });691+ .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() });
657 self.statics.insert((tyname.clone(), mname), nim);692 self.statics.insert((tyname.clone(), mname), nim);
658 }693 }
659 return Ok(());694 return Ok(());
@@ -661,6 +696,8 @@ impl Lowerer {
661 for it in &im.items {696 for it in &im.items {
662 if let syn::ImplItem::Fn(m) = it {697 if let syn::ImplItem::Fn(m) = it {
663 let (mut params, ret) = self.signature(&m.sig)?;698 let (mut params, ret) = self.signature(&m.sig)?;
699+ let mut gen_names = self.impl_generics.clone();
700+ gen_names.extend(Self::generics_of(&m.sig.generics));
664 if takes_self(&m.sig) {701 if takes_self(&m.sig) {
665 params.insert(0, self_ty.clone());702 params.insert(0, self_ty.clone());
666 }703 }
@@ -669,7 +706,7 @@ impl Lowerer {
669 let head = self.head_of(&nim, &m.sig, recv.as_ref())?;706 let head = self.head_of(&nim, &m.sig, recv.as_ref())?;
670 self.forwards.push(head);707 self.forwards.push(head);
671 self.methods708 self.methods
672- .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret });709+ .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret, generics: gen_names.clone() });
673 self.statics710 self.statics
674 .insert((tyname.clone(), m.sig.ident.to_string()), nim);711 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
675 }712 }
@@ -788,6 +825,22 @@ impl Lowerer {
788 Ok(self.subst_self(n))825 Ok(self.subst_self(n))
789 }826 }
790 827
828+ /// Substitute a generic type's parameters with the arguments the use site
829+ /// supplies: a field of `Holder<T>` read through a `Holder<i32>` is `i32`.
830+ fn subst_type_args(&self, name: &str, used_as: &Nim, field: Nim) -> Nim {
831+ let Some(params) = self.type_generics.get(name) else { return field };
832+ if params.is_empty() {
833+ return field;
834+ }
835+ let Nim::Named(n, args) = used_as else { return field };
836+ if n != name || args.len() != params.len() {
837+ return field;
838+ }
839+ let map: HashMap<String, Nim> =
840+ params.iter().cloned().zip(args.iter().cloned()).collect();
841+ Self::subst(&field, &map)
842+ }
843+
791 /// `Self` inside an `impl` block names the type being implemented.844 /// `Self` inside an `impl` block names the type being implemented.
792 fn subst_self(&self, t: Nim) -> Nim {845 fn subst_self(&self, t: Nim) -> Nim {
793 let Some(me) = &self.self_ty else { return t };846 let Some(me) = &self.self_ty else { return t };
@@ -841,6 +894,115 @@ impl Lowerer {
841 self.expand(&substitute(target, params, &args), depth + 1)894 self.expand(&substitute(target, params, &args), depth + 1)
842 }895 }
843 896
897+ /// The type parameters a generic item declares.
898+ ///
899+ /// Trait bounds and `where` clauses are dropped. Nim instantiates a
900+ /// generic structurally: an operation the bound would have permitted
901+ /// either exists for the instantiated type or is a compile error at the
902+ /// instantiation site. So dropping a bound cannot make an accepted
903+ /// program mean something different — it only makes rustnim accept some
904+ /// programs rustc would have rejected, which does not matter when the
905+ /// input is known-good Rust.
906+ fn generics_of(g: &syn::Generics) -> Vec<String> {
907+ g.params
908+ .iter()
909+ .filter_map(|p| match p {
910+ syn::GenericParam::Type(t) => Some(t.ident.to_string()),
911+ _ => None,
912+ })
913+ .collect()
914+ }
915+
916+ /// Bind a signature's type parameters by matching its declared parameter
917+ /// types against the actual argument types, then substitute into `ret`.
918+ ///
919+ /// This is the small amount of inference a call site needs: Nim will
920+ /// resolve the instantiation itself, but the *binding* still has to be
921+ /// annotated with a concrete type, and `T` is not one.
922+ fn instantiate(sig: &Sig, args: &[Option<Nim>]) -> Nim {
923+ if sig.generics.is_empty() {
924+ return sig.ret.clone();
925+ }
926+ let mut bound: HashMap<String, Nim> = HashMap::new();
927+ for (decl, actual) in sig.params.iter().zip(args) {
928+ if let Some(a) = actual {
929+ Self::unify(decl, a, &sig.generics, &mut bound);
930+ }
931+ }
932+ Self::subst(&sig.ret, &bound)
933+ }
934+
935+ fn unify(decl: &Nim, actual: &Nim, params: &[String], out: &mut HashMap<String, Nim>) {
936+ match (decl, actual) {
937+ (Nim::Named(n, da), _) if params.iter().any(|p| p == n) && da.is_empty() => {
938+ out.entry(n.clone()).or_insert_with(|| actual.clone());
939+ }
940+ (Nim::Named(_, da), Nim::Named(_, aa)) if da.len() == aa.len() => {
941+ for (d, a) in da.iter().zip(aa) {
942+ Self::unify(d, a, params, out);
943+ }
944+ }
945+ (Nim::Seq(d), Nim::Seq(a))
946+ | (Nim::OpenArray(d), Nim::OpenArray(a))
947+ | (Nim::Seq(d), Nim::OpenArray(a))
948+ | (Nim::OpenArray(d), Nim::Seq(a))
949+ | (Nim::Var(d), Nim::Var(a))
950+ | (Nim::Array(_, d), Nim::Array(_, a)) => Self::unify(d, a, params, out),
951+ (Nim::Var(d), a) => Self::unify(d, a, params, out),
952+ (d, Nim::Var(a)) => Self::unify(d, a, params, out),
953+ (Nim::Tuple(d), Nim::Tuple(a)) if d.len() == a.len() => {
954+ for (d, a) in d.iter().zip(a) {
955+ Self::unify(d, a, params, out);
956+ }
957+ }
958+ _ => {}
959+ }
960+ }
961+
962+ fn subst(t: &Nim, m: &HashMap<String, Nim>) -> Nim {
963+ match t {
964+ Nim::Named(n, a) if a.is_empty() => m.get(n).cloned().unwrap_or_else(|| t.clone()),
965+ Nim::Named(n, a) => {
966+ Nim::Named(n.clone(), a.iter().map(|x| Self::subst(x, m)).collect())
967+ }
968+ Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))),
969+ Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))),
970+ Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))),
971+ Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))),
972+ Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()),
973+ other => other.clone(),
974+ }
975+ }
976+
977+ /// Whether a type mentions a type parameter that is in scope here. Such a
978+ /// type cannot be used as a Nim annotation at an instantiation site: Nim
979+ /// infers it, and writing `T` would name something that is not bound.
980+ fn mentions_type_param(&self, t: &Nim) -> bool {
981+ match t {
982+ Nim::Named(n, a) => {
983+ self.fn_generics.iter().any(|g| g == n)
984+ || a.iter().any(|x| self.mentions_type_param(x))
985+ }
986+ Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) => {
987+ self.mentions_type_param(e)
988+ }
989+ Nim::Tuple(ts) => ts.iter().any(|x| self.mentions_type_param(x)),
990+ Nim::Proc(a, r) => {
991+ a.iter().any(|x| self.mentions_type_param(x)) || self.mentions_type_param(r)
992+ }
993+ _ => false,
994+ }
995+ }
996+
997+ /// `[T, U]`, or empty.
998+ fn gen_list(params: &[String]) -> String {
999+ if params.is_empty() {
1000+ String::new()
1001+ } else {
1002+ format!("[{}]", params.join(", "))
1003+ }
1004+ }
1005+
844 /// The Nim name for a function, qualified by its module.1006 /// The Nim name for a function, qualified by its module.
845 fn fn_name(&self, module: &str, name: &str) -> String {1007 fn fn_name(&self, module: &str, name: &str) -> String {
846 if module.is_empty() {1008 if module.is_empty() {
@@ -882,6 +1044,15 @@ impl Lowerer {
882 recv: Option<&Nim>,1044 recv: Option<&Nim>,
883 ) -> Result<String, String> {1045 ) -> Result<String, String> {
884 let (ptys, ret) = self.signature(sig)?;1046 let (ptys, ret) = self.signature(sig)?;
1047+ // A method inside `impl<T> Foo<T>` is generic in the impl's
1048+ // parameters as well as its own.
1049+ let mut params = self.impl_generics.clone();
1050+ for g in Self::generics_of(&sig.generics) {
1051+ if !params.contains(&g) {
1052+ params.push(g);
1053+ }
1054+ }
1055+ let gens = Self::gen_list(&params);
885 let mut parts = Vec::new();1056 let mut parts = Vec::new();
886 if let Some(self_ty) = recv {1057 if let Some(self_ty) = recv {
887 let mutable = matches!(1058 let mutable = matches!(
@@ -910,9 +1081,15 @@ impl Lowerer {
910 parts.push(format!("{}: {}", ident(&pname), t.render()));1081 parts.push(format!("{}: {}", ident(&pname), t.render()));
911 }1082 }
912 Ok(if ret == Nim::Unit {1083 Ok(if ret == Nim::Unit {
913- format!("proc {}*({})", ident(name), parts.join(", "))1084+ format!("proc {}*{}({})", ident(name), gens, parts.join(", "))
914 } else {1085 } else {
915- format!("proc {}*({}): {}", ident(name), parts.join(", "), ret.render())1086+ format!(
1087+ "proc {}*{}({}): {}",
1088+ ident(name),
1089+ gens,
1090+ parts.join(", "),
1091+ ret.render()
1092+ )
916 })1093 })
917 }1094 }
918 1095
@@ -923,15 +1100,12 @@ impl Lowerer {
923 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));1100 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
924 }1101 }
925 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so1102 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
926- // `fn encode<'a>(..)` is not generic for our purposes. Type and const1103+ // they disappear. Type parameters become Nim generic parameters.
927- // parameters genuinely are, and are rejected.1104+ // Const parameters have no Nim equivalent and are still rejected.
928- if let Some(p) = sig.generics.params.iter().find(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {1105+ if sig.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
929- let what = match p {
930- syn::GenericParam::Const(_) => "const",
931- _ => "type",
932- };
933 return Err(format!(1106 return Err(format!(
934- "`fn {}` has a {what} parameter: generics are not implemented yet",1107+ "`fn {}` has a const generic parameter, which Nim has no \
1108+ equivalent for",
935 sig.ident1109 sig.ident
936 ));1110 ));
937 }1111 }
@@ -997,7 +1171,8 @@ impl Lowerer {
997 Item::Struct(s) => {1171 Item::Struct(s) => {
998 let name = s.ident.to_string();1172 let name = s.ident.to_string();
999 let fields = self.structs[&name].clone();1173 let fields = self.structs[&name].clone();
1000- self.line(&format!("type {}* = object", ident(&name)));1174+ let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1175+ self.line(&format!("type {}*{} = object", ident(&name), g));
1001 self.indent += 1;1176 self.indent += 1;
1002 if fields.is_empty() {1177 if fields.is_empty() {
1003 self.line("discard");1178 self.line("discard");
@@ -1038,10 +1213,13 @@ impl Lowerer {
1038 Ok(())1213 Ok(())
1039 }1214 }
1040 Item::Impl(im) => {1215 Item::Impl(im) => {
1216+ let outer_g =
1217+ std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics));
1041 let self_ty = self.map_ty(&im.self_ty)?;1218 let self_ty = self.map_ty(&im.self_ty)?;
1042 let outer = self.self_ty.replace(self_ty.clone());1219 let outer = self.self_ty.replace(self_ty.clone());
1043 let r = self.impl_body(im, &self_ty);1220 let r = self.impl_body(im, &self_ty);
1044 self.self_ty = outer;1221 self.self_ty = outer;
1222+ self.impl_generics = outer_g;
1045 r1223 r
1046 }1224 }
1047 // `use` and `extern crate` are resolution directives with no Nim1225 // `use` and `extern crate` are resolution directives with no Nim
@@ -1149,7 +1327,16 @@ impl Lowerer {
1149 1327
1150 fn emit_enum(&mut self, def: &EnumDef) {1328 fn emit_enum(&mut self, def: &EnumDef) {
1151 let name = ident(&def.name);1329 let name = ident(&def.name);
1152- if def.simple {1330+ let g = Self::gen_list(
1331+ self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
1332+ );
1333+ if def.simple && !g.is_empty() {
1334+ // A Nim `enum` cannot take parameters; an all-unit generic enum
1335+ // has no payload to be generic in anyway, so this would be a
1336+ // parameter that never appears.
1337+ // Fall through to the object-variant form instead.
1338+ }
1339+ if def.simple && g.is_empty() {
1153 // Every variant is a unit variant, so a plain Nim enum is an exact1340 // Every variant is a unit variant, so a plain Nim enum is an exact
1154 // fit: it compares, orders and `case`-checks like Rust's.1341 // fit: it compares, orders and `case`-checks like Rust's.
1155 self.line(&format!("type {name}* = enum"));1342 self.line(&format!("type {name}* = enum"));
@@ -1182,7 +1369,7 @@ impl Lowerer {
1182 }1369 }
1183 self.indent -= 1;1370 self.indent -= 1;
1184 self.blank();1371 self.blank();
1185- self.line(&format!("{}* = object", name));1372+ self.line(&format!("{}*{} = object", name, g));
1186 self.indent += 1;1373 self.indent += 1;
1187 self.line(&format!("case kind*: {}Kind", name));1374 self.line(&format!("case kind*: {}Kind", name));
1188 for v in &def.variants {1375 for v in &def.variants {
@@ -1215,18 +1402,20 @@ impl Lowerer {
1215 .collect();1402 .collect();
1216 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];1403 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1217 all.extend(inits);1404 all.extend(inits);
1405+ let ret = format!("{}{}", name, g);
1218 self.line(&format!(1406 self.line(&format!(
1219- "proc {}*({}): {} = {}({})",1407+ "proc {}*{}({}): {} = {}({})",
1220 def.ctor_ident(&v.name),1408 def.ctor_ident(&v.name),
1409+ g,
1221 args.join(", "),1410 args.join(", "),
1222- name,1411+ ret,
1223- name,1412+ ret,
1224 all.join(", ")1413 all.join(", ")
1225 ));1414 ));
1226 }1415 }
1227 self.blank();1416 self.blank();
1228 1417
1229- self.line(&format!("proc rsDebug*(x: {name}): string ="));1418+ self.line(&format!("proc rsDebug*{}(x: {}{}): string =", g, name, g));
1230 self.indent += 1;1419 self.indent += 1;
1231 self.line("case x.kind");1420 self.line("case x.kind");
1232 for v in &def.variants {1421 for v in &def.variants {
@@ -1250,6 +1439,30 @@ impl Lowerer {
1250 self.blank();1439 self.blank();
1251 }1440 }
1252 1441
1442+ /// The concrete type an enum variant constructs, and the `[T]` list to
1443+ /// spell at the constructor when the enum is generic.
1444+ fn variant_type(
1445+ &self,
1446+ def: &EnumDef,
1447+ expect: Option<&Nim>,
1448+ ) -> Result<(Nim, String), String> {
1449+ let params = self.type_generics.get(&def.name).cloned().unwrap_or_default();
1450+ if params.is_empty() {
1451+ return Ok((Nim::Named(def.name.clone(), vec![]), String::new()));
1452+ }
1453+ match expect {
1454+ Some(Nim::Named(n, a)) if *n == def.name && a.len() == params.len() => Ok((
1455+ Nim::Named(def.name.clone(), a.clone()),
1456+ format!("[{}]", a.iter().map(|t| t.render()).collect::<Vec<_>>().join(", ")),
1457+ )),
1458+ _ => Err(format!(
1459+ "`{}` is a variant of a generic enum, and its type parameters \
1460+ cannot be inferred here; annotate the binding or the return type",
1461+ def.name
1462+ )),
1463+ }
1464+ }
1465+
1253 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`1466 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1254 /// to the enum that declares it.1467 /// to the enum that declares it.
1255 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {1468 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
@@ -1416,10 +1629,24 @@ impl Lowerer {
1416 self.bind(&pname, t.clone().owned());1629 self.bind(&pname, t.clone().owned());
1417 }1630 }
1418 1631
1632+ let mut gparams = self.impl_generics.clone();
1633+ for g in Self::generics_of(&sig.generics) {
1634+ if !gparams.contains(&g) {
1635+ gparams.push(g);
1636+ }
1637+ }
1638+ let gens = Self::gen_list(&gparams);
1639+ let outer_fg = std::mem::replace(&mut self.fn_generics, gparams.clone());
1419 let head = if ret == Nim::Unit {1640 let head = if ret == Nim::Unit {
1420- format!("proc {}*({}) =", ident(name), rendered.join(", "))1641+ format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
1421 } else {1642 } else {
1422- format!("proc {}*({}): {} =", ident(name), rendered.join(", "), ret.render())1643+ format!(
1644+ "proc {}*{}({}): {} =",
1645+ ident(name),
1646+ gens,
1647+ rendered.join(", "),
1648+ ret.render()
1649+ )
1423 };1650 };
1424 self.line(&head);1651 self.line(&head);
1425 self.indent += 1;1652 self.indent += 1;
@@ -1458,6 +1685,7 @@ impl Lowerer {
1458 1685
1459 self.indent -= 1;1686 self.indent -= 1;
1460 self.ret = outer_ret;1687 self.ret = outer_ret;
1688+ self.fn_generics = outer_fg;
1461 self.pop_scope();1689 self.pop_scope();
1462 self.blank();1690 self.blank();
1463 Ok(())1691 Ok(())
@@ -1669,7 +1897,13 @@ impl Lowerer {
1669 // `var` parameter is wanted, so the binding has to be one.1897 // `var` parameter is wanted, so the binding has to be one.
1670 let mutable = mutable || is_mut_borrow(&init.expr);1898 let mutable = mutable || is_mut_borrow(&init.expr);
1671 let kw = if mutable { "var" } else { "let" };1899 let kw = if mutable { "var" } else { "let" };
1672- let line = format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code);1900+ // Inside a generic proc the binding's type may mention a parameter Nim
1901+ // will infer; naming it in an annotation would not resolve.
1902+ let line = if self.mentions_type_param(&t) {
1903+ format!("{} {} = {}", kw, ident(&name), v.code)
1904+ } else {
1905+ format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)
1906+ };
1673 self.line(&line);1907 self.line(&line);
1674 self.bind(&name, t);1908 self.bind(&name, t);
1675 Ok(())1909 Ok(())
@@ -2436,7 +2670,13 @@ impl Lowerer {
2436 let Some((def, v)) = self.resolve_variant(path) else {2670 let Some((def, v)) = self.resolve_variant(path) else {
2437 return Err(format!("`{last}` is not a known enum variant"));2671 return Err(format!("`{last}` is not a known enum variant"));
2438 };2672 };
2439- Ok(def.get(&v).map(|v| v.fields.clone()).unwrap_or_default())2673+ let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2674+ // The variant's payload is declared in the enum's own parameters; the
2675+ // scrutinee says what they are here.
2676+ Ok(fields
2677+ .into_iter()
2678+ .map(|(n, ft)| (n, self.subst_type_args(&def.name, t, ft)))
2679+ .collect())
2440 }2680 }
2441 2681
2442 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {2682 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
@@ -2529,6 +2769,21 @@ impl Lowerer {
2529 if name == "None" {2769 if name == "None" {
2530 return Ok(Val::new(self.none_of(expect), expect.cloned()));2770 return Ok(Val::new(self.none_of(expect), expect.cloned()));
2531 }2771 }
2772+ // `i32::MAX` and friends: an associated const on a primitive.
2773+ if matches!(name.as_str(), "MAX" | "MIN") {
2774+ if let Some(q) = p.path.segments.iter().rev().nth(1) {
2775+ if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) {
2776+ if t.is_integer() {
2777+ let f = if name == "MAX" { "high" } else { "low" };
2778+ return Ok(Val::new(
2779+ format!("{}({})", f, t.render()),
2780+ Some(t),
2781+ ));
2782+ }
2783+ }
2784+ }
2785+ }
2786+
2532 // A unit struct used as a value: `fmt::Error`, or a `struct S;`2787 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
2533 // declared here. In Nim that is a constructor call.2788 // declared here. In Nim that is a constructor call.
2534 if p.path.segments.len() > 1 {2789 if p.path.segments.len() > 1 {
@@ -2547,11 +2802,16 @@ impl Lowerer {
2547 }2802 }
2548 // A unit enum variant used as a value: `Error::InvalidLength`.2803 // A unit enum variant used as a value: `Error::InvalidLength`.
2549 if let Some((def, v)) = self.resolve_variant(&p.path) {2804 if let Some((def, v)) = self.resolve_variant(&p.path) {
2550- let ty = Some(Nim::Named(def.name.clone(), vec![]));2805+ let (ty, targs) = self.variant_type(&def, expect)?;
2551- return Ok(if def.simple {2806+ return Ok(if def.simple && targs.is_empty() {
2552- Val::new(format!("{}.{}", ident(&def.name), ident(&v)), ty)2807+ Val::new(format!("{}.{}", ident(&def.name), ident(&v)), Some(ty))
2553 } else {2808 } else {
2554- Val::new(format!("{}()", def.ctor_ident(&v)), ty)2809+ // A unit variant of a generic enum has no argument to
2810+ // infer the parameters from, so they are written out.
2811+ Val::new(
2812+ format!("{}{}()", def.ctor_ident(&v), targs),
2813+ Some(ty),
2814+ )
2555 });2815 });
2556 }2816 }
2557 // A `for` binding that stands for an element of the container2817 // A `for` binding that stands for an element of the container
@@ -2653,11 +2913,11 @@ impl Lowerer {
2653 syn::Member::Unnamed(i) => format!("f{}", i.index),2913 syn::Member::Unnamed(i) => format!("f{}", i.index),
2654 };2914 };
2655 let t = match &base.ty {2915 let t = match &base.ty {
2656- Some(Nim::Named(s, _)) => self2916+ Some(bt @ Nim::Named(s, _)) => self
2657 .structs2917 .structs
2658 .get(s)2918 .get(s)
2659 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))2919 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
2660- .map(|(_, t)| t.clone()),2920+ .map(|(_, t)| self.subst_type_args(s, bt, t.clone())),
2661 _ => None,2921 _ => None,
2662 };2922 };
2663 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))2923 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
@@ -2732,7 +2992,16 @@ impl Lowerer {
2732 Some(Nim::Named(def.name.clone(), vec![])),2992 Some(Nim::Named(def.name.clone(), vec![])),
2733 ));2993 ));
2734 }2994 }
2735- let name = path_name(&s.path);2995+ // `Self { .. }` inside an `impl` names the type being
2996+ // implemented, and its fields are that type's fields.
2997+ let name = match path_name(&s.path).as_str() {
2998+ "Self" => self
2999+ .self_ty
3000+ .as_ref()
3001+ .map(type_name)
3002+ .ok_or("`Self` outside an `impl` block")?,
3003+ other => other.to_string(),
3004+ };
2736 let mut parts = Vec::new();3005 let mut parts = Vec::new();
2737 for f in &s.fields {3006 for f in &s.fields {
2738 let fname = match &f.member {3007 let fname = match &f.member {
@@ -2747,9 +3016,28 @@ impl Lowerer {
2747 let v = self.expr_at(&f.expr, want.as_ref())?;3016 let v = self.expr_at(&f.expr, want.as_ref())?;
2748 parts.push(format!("{}: {}", ident(&fname), v.code));3017 parts.push(format!("{}: {}", ident(&fname), v.code));
2749 }3018 }
3019+ // Nim cannot infer an object's generic parameters from a
3020+ // constructor's field values, so they are written out.
3021+ let gp = self.type_generics.get(&name).cloned().unwrap_or_default();
3022+ let ty = if gp.is_empty() {
3023+ Nim::Named(name.clone(), vec![])
3024+ } else {
3025+ match expect {
3026+ Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => {
3027+ Nim::Named(name.clone(), a.clone())
3028+ }
3029+ _ => {
3030+ return Err(format!(
3031+ "`{name} {{ .. }}` is generic, and Nim cannot infer \
3032+ its parameters from the field values; annotate the \
3033+ binding or the return type"
3034+ ))
3035+ }
3036+ }
3037+ };
2750 Ok(Val::new(3038 Ok(Val::new(
2751- format!("{}({})", ident(&name), parts.join(", ")),3039+ format!("{}({})", ty.render(), parts.join(", ")),
2752- Some(Nim::Named(name, vec![])),3040+ Some(ty),
2753 ))3041 ))
2754 }3042 }
2755 Expr::Array(a) => {3043 Expr::Array(a) => {
@@ -3446,6 +3734,27 @@ impl Lowerer {
3446 }3734 }
3447 }3735 }
3448 3736
3737+ // `Spacing::from(d)`: a `From` impl called through its target type.
3738+ // Rust picks the impl by the argument's type, and so do we -- Nim
3739+ // cannot overload on return type, so each impl has its own proc name.
3740+ if name == "from" && codes.len() == 1 {
3741+ if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3742+ let q = if q == "Self" {
3743+ self.self_ty.as_ref().map(type_name).unwrap_or(q)
3744+ } else {
3745+ q
3746+ };
3747+ if let Some(src) = args[0].ty.as_ref().map(type_name) {
3748+ if let Some(f) = self.from_impls.get(&(src, q.clone())).cloned() {
3749+ return Ok(Val::new(
3750+ format!("{}({})", f, codes[0]),
3751+ Some(Nim::Named(q, vec![])),
3752+ ));
3753+ }
3754+ }
3755+ }
3756+ }
3757+
3449 // `u32::from(b)`: `From` between primitives is lossless by definition3758 // `u32::from(b)`: `From` between primitives is lossless by definition
3450 // -- it is the widening direction only -- so a plain Nim conversion is3759 // -- it is the widening direction only -- so a plain Nim conversion is
3451 // exact. (The truncating direction is `as`, which is `cast`.)3760 // exact. (The truncating direction is `as`, which is `cast`.)
@@ -3494,9 +3803,10 @@ impl Lowerer {
3494 3803
3495 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.3804 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
3496 if let Some((def, v)) = self.resolve_variant(&p.path) {3805 if let Some((def, v)) = self.resolve_variant(&p.path) {
3806+ let (ty, _) = self.variant_type(&def, expect)?;
3497 return Ok(Val::new(3807 return Ok(Val::new(
3498 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),3808 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
3499- Some(Nim::Named(def.name.clone(), vec![])),3809+ Some(ty),
3500 ));3810 ));
3501 }3811 }
3502 3812
@@ -3520,7 +3830,8 @@ impl Lowerer {
3520 q3830 q
3521 };3831 };
3522 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {3832 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
3523- let ret = sig.ret.clone();3833+ let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
3834+ let ret = Self::instantiate(sig, &arg_tys);
3524 let nim = self3835 let nim = self
3525 .statics3836 .statics
3526 .get(&(q.clone(), name.clone()))3837 .get(&(q.clone(), name.clone()))
@@ -3529,7 +3840,11 @@ impl Lowerer {
3529 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));3840 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
3530 }3841 }
3531 }3842 }
3532- let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone());3843+ let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
3844+ let ret = target
3845+ .as_ref()
3846+ .and_then(|k| self.fns.get(k))
3847+ .map(|sig| Self::instantiate(sig, &arg_tys));
3533 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {3848 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
3534 return Err(format!(3849 return Err(format!(
3535 "call to unknown function `{name}`; only functions defined in \3850 "call to unknown function `{name}`; only functions defined in \
@@ -3822,6 +4137,30 @@ impl Lowerer {
3822 };4137 };
3823 (format!("result.add({})", text), Some(Nim::Unit))4138 (format!("result.add({})", text), Some(Nim::Unit))
3824 }4139 }
4140+ "saturating_add" | "saturating_sub" | "saturating_mul" | "checked_add"
4141+ | "checked_sub" | "checked_mul" => {
4142+ let t = rt
4143+ .clone()
4144+ .filter(|t| t.is_integer())
4145+ .ok_or_else(|| format!("`{name}` needs a known integer receiver"))?;
4146+ let arg = args
4147+ .first()
4148+ .ok_or_else(|| format!("`{name}` takes one argument"))?;
4149+ let f = match name.as_str() {
4150+ "saturating_add" => "rsSatAdd",
4151+ "saturating_sub" => "rsSatSub",
4152+ "saturating_mul" => "rsSatMul",
4153+ "checked_add" => "rsChkAdd",
4154+ "checked_sub" => "rsChkSub",
4155+ _ => "rsChkMul",
4156+ };
4157+ let out = if name.starts_with("checked") {
4158+ Nim::Named("Option".into(), vec![t])
4159+ } else {
4160+ t
4161+ };
4162+ (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
4163+ }
3825 "abs" => (format!("abs({})", recv.code), rt.clone()),4164 "abs" => (format!("abs({})", recv.code), rt.clone()),
3826 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),4165 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
3827 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),4166 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
@@ -3853,10 +4192,12 @@ impl Lowerer {
3853 // A method defined in this file via `impl`, found by the4192 // A method defined in this file via `impl`, found by the
3854 // receiver's type rather than by name alone.4193 // receiver's type rather than by name alone.
3855 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));4194 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
4195+ let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
4196+ arg_tys.extend(args.iter().map(|a| a.ty.clone()));
3856 let sig = key4197 let sig = key
3857 .as_ref()4198 .as_ref()
3858 .and_then(|k| self.methods.get(k))4199 .and_then(|k| self.methods.get(k))
3859- .map(|s| s.ret.clone());4200+ .map(|s| Self::instantiate(s, &arg_tys));
3860 if let Some(ret) = sig {4201 if let Some(ret) = sig {
3861 // Use the name the proc was actually emitted under: an4202 // Use the name the proc was actually emitted under: an
3862 // inherent method is qualified by its module, a trait4203 // inherent method is qualified by its module, a trait
modified src/prelude.nim +60 -0
@@ -57,6 +57,66 @@ proc unwrap*[T, E](r: Result[T, E]): T =
5757 if not r.ok: rsPanic("called `Result::unwrap()` on an `Err` value")
5858 r.val
5959
60+# ---------------------------------------------------------------------------
61+# Rust's explicit overflow policies.
62+#
63+# Plain `+` on a signed integer traps in both languages (DESIGN.md item 3), and
64+# on an unsigned one wraps in both (item 2). `saturating_*` and `checked_*` are
65+# neither, so they are spelled out. Overflow is detected on the unsigned view
66+# of the same width, where wrapping is defined, rather than by a range check
67+# that would itself trap.
68+# ---------------------------------------------------------------------------
69+
70+proc rsSatAdd*[T: SomeInteger](a, b: T): T =
71+ when T is SomeUnsignedInt:
72+ let s = a + b
73+ if s < a: high(T) else: s
74+ else:
75+ let s = cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b)))
76+ # Overflow iff the operands agree in sign and the result disagrees.
77+ if (a >= 0) == (b >= 0) and (s >= 0) != (a >= 0):
78+ if a >= 0: high(T) else: low(T)
79+ else: s
80+
81+proc rsSatSub*[T: SomeInteger](a, b: T): T =
82+ when T is SomeUnsignedInt:
83+ if a < b: T(0) else: a - b
84+ else:
85+ let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b)))
86+ if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0):
87+ if a >= 0: high(T) else: low(T)
88+ else: s
89+
90+proc rsSatMul*[T: SomeInteger](a, b: T): T =
91+ if a == T(0) or b == T(0): return T(0)
92+ let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b)))
93+ if s div b != a:
94+ when T is SomeUnsignedInt: high(T)
95+ else: (if (a >= 0) == (b >= 0): high(T) else: low(T))
96+ else: s
97+
98+proc rsChkAdd*[T: SomeInteger](a, b: T): Option[T] =
99+ let s = rsSatAdd(a, b)
100+ when T is SomeUnsignedInt:
101+ if s == high(T) and not (a + b == high(T)): rsNone[T]() else: rsSome(s)
102+ else:
103+ if (a >= 0) == (b >= 0) and (s == high(T) or s == low(T)) and
104+ cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b))) != s:
105+ rsNone[T]()
106+ else: rsSome(s)
107+
108+proc rsChkSub*[T: SomeInteger](a, b: T): Option[T] =
109+ when T is SomeUnsignedInt:
110+ if a < b: rsNone[T]() else: rsSome(a - b)
111+ else:
112+ let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b)))
113+ if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0): rsNone[T]() else: rsSome(s)
114+
115+proc rsChkMul*[T: SomeInteger](a, b: T): Option[T] =
116+ if a == T(0) or b == T(0): return rsSome(T(0))
117+ let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b)))
118+ if s div b != a: rsNone[T]() else: rsSome(s)
119+
60120 # ---------------------------------------------------------------------------
61121 # Display / Debug.
62122 #
@@ -57,6 +57,66 @@ proc unwrap*[T, E](r: Result[T, E]): T =
57 if not r.ok: rsPanic("called `Result::unwrap()` on an `Err` value")57 if not r.ok: rsPanic("called `Result::unwrap()` on an `Err` value")
58 r.val58 r.val
59 59
60+# ---------------------------------------------------------------------------
61+# Rust's explicit overflow policies.
62+#
63+# Plain `+` on a signed integer traps in both languages (DESIGN.md item 3), and
64+# on an unsigned one wraps in both (item 2). `saturating_*` and `checked_*` are
65+# neither, so they are spelled out. Overflow is detected on the unsigned view
66+# of the same width, where wrapping is defined, rather than by a range check
67+# that would itself trap.
68+# ---------------------------------------------------------------------------
69+
70+proc rsSatAdd*[T: SomeInteger](a, b: T): T =
71+ when T is SomeUnsignedInt:
72+ let s = a + b
73+ if s < a: high(T) else: s
74+ else:
75+ let s = cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b)))
76+ # Overflow iff the operands agree in sign and the result disagrees.
77+ if (a >= 0) == (b >= 0) and (s >= 0) != (a >= 0):
78+ if a >= 0: high(T) else: low(T)
79+ else: s
80+
81+proc rsSatSub*[T: SomeInteger](a, b: T): T =
82+ when T is SomeUnsignedInt:
83+ if a < b: T(0) else: a - b
84+ else:
85+ let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b)))
86+ if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0):
87+ if a >= 0: high(T) else: low(T)
88+ else: s
89+
90+proc rsSatMul*[T: SomeInteger](a, b: T): T =
91+ if a == T(0) or b == T(0): return T(0)
92+ let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b)))
93+ if s div b != a:
94+ when T is SomeUnsignedInt: high(T)
95+ else: (if (a >= 0) == (b >= 0): high(T) else: low(T))
96+ else: s
97+
98+proc rsChkAdd*[T: SomeInteger](a, b: T): Option[T] =
99+ let s = rsSatAdd(a, b)
100+ when T is SomeUnsignedInt:
101+ if s == high(T) and not (a + b == high(T)): rsNone[T]() else: rsSome(s)
102+ else:
103+ if (a >= 0) == (b >= 0) and (s == high(T) or s == low(T)) and
104+ cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b))) != s:
105+ rsNone[T]()
106+ else: rsSome(s)
107+
108+proc rsChkSub*[T: SomeInteger](a, b: T): Option[T] =
109+ when T is SomeUnsignedInt:
110+ if a < b: rsNone[T]() else: rsSome(a - b)
111+ else:
112+ let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b)))
113+ if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0): rsNone[T]() else: rsSome(s)
114+
115+proc rsChkMul*[T: SomeInteger](a, b: T): Option[T] =
116+ if a == T(0) or b == T(0): return rsSome(T(0))
117+ let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b)))
118+ if s div b != a: rsNone[T]() else: rsSome(s)
119+
60 # ---------------------------------------------------------------------------120 # ---------------------------------------------------------------------------
61 # Display / Debug.121 # Display / Debug.
62 #122 #
added tests/cases/030-generics.rs +65 -0
new file mode 100644
@@ -0,0 +1,65 @@
1+// Rust generics map to Nim's, which are instantiated structurally at the call
2+// site much as Rust's are. Trait bounds and `where` clauses are dropped: an
3+// operation the bound permitted either exists for the instantiated type or is
4+// a compile error at that instantiation, so dropping it cannot change what an
5+// accepted program means.
6+#[derive(Copy, Clone)]
7+struct Pair<T> {
8+ a: T,
9+ b: T,
10+}
11+
12+enum Holder<T> {
13+ Empty,
14+ One(T),
15+}
16+
17+impl<T: Copy> Pair<T> {
18+ fn first(&self) -> T {
19+ self.a
20+ }
21+ fn swapped(&self) -> Pair<T> {
22+ Pair { a: self.b, b: self.a }
23+ }
24+}
25+
26+fn largest<T: PartialOrd + Copy>(xs: &[T]) -> T {
27+ let mut m: T = xs[0];
28+ for x in xs.iter() {
29+ if *x > m {
30+ m = *x;
31+ }
32+ }
33+ m
34+}
35+
36+fn count<T>(h: &Holder<T>) -> i32 {
37+ match h {
38+ Holder::Empty => 0,
39+ Holder::One(_) => 1,
40+ }
41+}
42+
43+fn main() {
44+ let p: Pair<i32> = Pair { a: 1, b: 2 };
45+ let q: Pair<i32> = p.swapped();
46+ println!("{} {} {}", q.a, q.b, p.first());
47+
48+ let f: Pair<f64> = Pair { a: 1.5, b: -0.5 };
49+ println!("{} {}", f.swapped().a, f.first());
50+
51+ let v: Vec<i32> = vec![3, 9, 4];
52+ println!("{}", largest(&v));
53+ let w: Vec<f64> = vec![1.5, 0.25, 9.75];
54+ println!("{}", largest(&w));
55+ let b: Vec<u8> = vec![7, 200, 3];
56+ println!("{}", largest(&b));
57+
58+ let h: Holder<i32> = Holder::One(7);
59+ let e: Holder<i32> = Holder::Empty;
60+ println!("{} {}", count(&h), count(&e));
61+ match h {
62+ Holder::Empty => println!("empty"),
63+ Holder::One(v) => println!("one {}", v),
64+ }
65+}
new file mode 100644
@@ -0,0 +1,65 @@
1+// Rust generics map to Nim's, which are instantiated structurally at the call
2+// site much as Rust's are. Trait bounds and `where` clauses are dropped: an
3+// operation the bound permitted either exists for the instantiated type or is
4+// a compile error at that instantiation, so dropping it cannot change what an
5+// accepted program means.
6+#[derive(Copy, Clone)]
7+struct Pair<T> {
8+ a: T,
9+ b: T,
10+}
11+
12+enum Holder<T> {
13+ Empty,
14+ One(T),
15+}
16+
17+impl<T: Copy> Pair<T> {
18+ fn first(&self) -> T {
19+ self.a
20+ }
21+ fn swapped(&self) -> Pair<T> {
22+ Pair { a: self.b, b: self.a }
23+ }
24+}
25+
26+fn largest<T: PartialOrd + Copy>(xs: &[T]) -> T {
27+ let mut m: T = xs[0];
28+ for x in xs.iter() {
29+ if *x > m {
30+ m = *x;
31+ }
32+ }
33+ m
34+}
35+
36+fn count<T>(h: &Holder<T>) -> i32 {
37+ match h {
38+ Holder::Empty => 0,
39+ Holder::One(_) => 1,
40+ }
41+}
42+
43+fn main() {
44+ let p: Pair<i32> = Pair { a: 1, b: 2 };
45+ let q: Pair<i32> = p.swapped();
46+ println!("{} {} {}", q.a, q.b, p.first());
47+
48+ let f: Pair<f64> = Pair { a: 1.5, b: -0.5 };
49+ println!("{} {}", f.swapped().a, f.first());
50+
51+ let v: Vec<i32> = vec![3, 9, 4];
52+ println!("{}", largest(&v));
53+ let w: Vec<f64> = vec![1.5, 0.25, 9.75];
54+ println!("{}", largest(&w));
55+ let b: Vec<u8> = vec![7, 200, 3];
56+ println!("{}", largest(&b));
57+
58+ let h: Holder<i32> = Holder::One(7);
59+ let e: Holder<i32> = Holder::Empty;
60+ println!("{} {}", count(&h), count(&e));
61+ match h {
62+ Holder::Empty => println!("empty"),
63+ Holder::One(v) => println!("one {}", v),
64+ }
65+}
added tests/cases/031-saturating-checked.rs +15 -0
new file mode 100644
@@ -0,0 +1,15 @@
1+// Rust's explicit overflow policies. Plain `+` traps on signed and wraps on
2+// unsigned in both languages; these are neither, so they are spelled out.
3+fn main() {
4+ println!("{} {}", 250u8.saturating_add(10), 5u8.saturating_sub(10));
5+ println!("{} {}", 200u8.saturating_mul(2), 100u8.saturating_mul(2));
6+ println!("{} {}", 127i8.saturating_add(1), (-128i8).saturating_sub(1));
7+ println!("{} {}", 127i8.saturating_mul(2), (-128i8).saturating_mul(2));
8+ println!("{} {}", i32::MAX.saturating_add(1), i32::MIN.saturating_sub(1));
9+ println!("{:?} {:?}", 250u8.checked_add(5), 250u8.checked_add(10));
10+ println!("{:?} {:?}", 5u8.checked_sub(5), 5u8.checked_sub(6));
11+ println!("{:?} {:?}", 100u8.checked_mul(2), 200u8.checked_mul(2));
12+ println!("{:?} {:?}", 127i8.checked_add(0), 127i8.checked_add(1));
13+ println!("{:?} {:?}", (-128i8).checked_sub(0), (-128i8).checked_sub(1));
14+ println!("{} {}", 16u16.saturating_sub(16), 16u16.saturating_sub(24));
15+}
new file mode 100644
@@ -0,0 +1,15 @@
1+// Rust's explicit overflow policies. Plain `+` traps on signed and wraps on
2+// unsigned in both languages; these are neither, so they are spelled out.
3+fn main() {
4+ println!("{} {}", 250u8.saturating_add(10), 5u8.saturating_sub(10));
5+ println!("{} {}", 200u8.saturating_mul(2), 100u8.saturating_mul(2));
6+ println!("{} {}", 127i8.saturating_add(1), (-128i8).saturating_sub(1));
7+ println!("{} {}", 127i8.saturating_mul(2), (-128i8).saturating_mul(2));
8+ println!("{} {}", i32::MAX.saturating_add(1), i32::MIN.saturating_sub(1));
9+ println!("{:?} {:?}", 250u8.checked_add(5), 250u8.checked_add(10));
10+ println!("{:?} {:?}", 5u8.checked_sub(5), 5u8.checked_sub(6));
11+ println!("{:?} {:?}", 100u8.checked_mul(2), 200u8.checked_mul(2));
12+ println!("{:?} {:?}", 127i8.checked_add(0), 127i8.checked_add(1));
13+ println!("{:?} {:?}", (-128i8).checked_sub(0), (-128i8).checked_sub(1));
14+ println!("{} {}", 16u16.saturating_sub(16), 16u16.saturating_sub(24));
15+}
added tests/cases/032-cosmic-theme-spacing/corner.rs +78 -0
new file mode 100644
@@ -0,0 +1,78 @@
1+
2+/// Corner radii variables for the Cosmic theme
3+#[derive(Debug, Copy, Clone, PartialEq)]
4+pub struct CornerRadii {
5+ /// corner radii of 0
6+ pub radius_0: [f32; 4],
7+ /// smallest size of corner radii that can be non-zero
8+ pub radius_xs: [f32; 4],
9+ /// small corner radii
10+ pub radius_s: [f32; 4],
11+ /// medium corner radii
12+ pub radius_m: [f32; 4],
13+ /// large corner radii
14+ pub radius_l: [f32; 4],
15+ /// extra large corner radii
16+ pub radius_xl: [f32; 4],
17+}
18+
19+impl Default for CornerRadii {
20+ fn default() -> Self {
21+ Self {
22+ radius_0: [0.0; 4],
23+ radius_xs: [4.0; 4],
24+ radius_s: [8.0; 4],
25+ radius_m: [16.0; 4],
26+ radius_l: [32.0; 4],
27+ radius_xl: [160.0; 4],
28+ }
29+ }
30+}
31+
32+/// Roundness options for the Cosmic theme
33+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
34+pub enum Roundness {
35+ /// Round style
36+ #[default]
37+ Round,
38+ /// Slightly round style
39+ SlightlyRound,
40+ /// Square style
41+ Square,
42+}
43+
44+impl From<Roundness> for CornerRadii {
45+ fn from(value: Roundness) -> Self {
46+ match value {
47+ Roundness::Round => CornerRadii::default(),
48+ Roundness::SlightlyRound => CornerRadii {
49+ radius_0: [0.0; 4],
50+ radius_xs: [2.0; 4],
51+ radius_s: [8.0; 4],
52+ radius_m: [8.0; 4],
53+ radius_l: [8.0; 4],
54+ radius_xl: [8.0; 4],
55+ },
56+ Roundness::Square => CornerRadii {
57+ radius_0: [0.0; 4],
58+ radius_xs: [2.0; 4],
59+ radius_s: [2.0; 4],
60+ radius_m: [2.0; 4],
61+ radius_l: [2.0; 4],
62+ radius_xl: [2.0; 4],
63+ },
64+ }
65+ }
66+}
67+
68+impl From<CornerRadii> for Roundness {
69+ fn from(value: CornerRadii) -> Self {
70+ if (value.radius_m[0] - 16.0).abs() < 0.01 {
71+ Self::Round
72+ } else if (value.radius_m[0] - 8.0).abs() < 0.01 {
73+ Self::SlightlyRound
74+ } else {
75+ Self::Square
76+ }
77+ }
78+}
new file mode 100644
@@ -0,0 +1,78 @@
1+
2+/// Corner radii variables for the Cosmic theme
3+#[derive(Debug, Copy, Clone, PartialEq)]
4+pub struct CornerRadii {
5+ /// corner radii of 0
6+ pub radius_0: [f32; 4],
7+ /// smallest size of corner radii that can be non-zero
8+ pub radius_xs: [f32; 4],
9+ /// small corner radii
10+ pub radius_s: [f32; 4],
11+ /// medium corner radii
12+ pub radius_m: [f32; 4],
13+ /// large corner radii
14+ pub radius_l: [f32; 4],
15+ /// extra large corner radii
16+ pub radius_xl: [f32; 4],
17+}
18+
19+impl Default for CornerRadii {
20+ fn default() -> Self {
21+ Self {
22+ radius_0: [0.0; 4],
23+ radius_xs: [4.0; 4],
24+ radius_s: [8.0; 4],
25+ radius_m: [16.0; 4],
26+ radius_l: [32.0; 4],
27+ radius_xl: [160.0; 4],
28+ }
29+ }
30+}
31+
32+/// Roundness options for the Cosmic theme
33+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
34+pub enum Roundness {
35+ /// Round style
36+ #[default]
37+ Round,
38+ /// Slightly round style
39+ SlightlyRound,
40+ /// Square style
41+ Square,
42+}
43+
44+impl From<Roundness> for CornerRadii {
45+ fn from(value: Roundness) -> Self {
46+ match value {
47+ Roundness::Round => CornerRadii::default(),
48+ Roundness::SlightlyRound => CornerRadii {
49+ radius_0: [0.0; 4],
50+ radius_xs: [2.0; 4],
51+ radius_s: [8.0; 4],
52+ radius_m: [8.0; 4],
53+ radius_l: [8.0; 4],
54+ radius_xl: [8.0; 4],
55+ },
56+ Roundness::Square => CornerRadii {
57+ radius_0: [0.0; 4],
58+ radius_xs: [2.0; 4],
59+ radius_s: [2.0; 4],
60+ radius_m: [2.0; 4],
61+ radius_l: [2.0; 4],
62+ radius_xl: [2.0; 4],
63+ },
64+ }
65+ }
66+}
67+
68+impl From<CornerRadii> for Roundness {
69+ fn from(value: CornerRadii) -> Self {
70+ if (value.radius_m[0] - 16.0).abs() < 0.01 {
71+ Self::Round
72+ } else if (value.radius_m[0] - 8.0).abs() < 0.01 {
73+ Self::SlightlyRound
74+ } else {
75+ Self::Square
76+ }
77+ }
78+}
added tests/cases/032-cosmic-theme-spacing/layout.rs +4 -0
new file mode 100644
@@ -0,0 +1,4 @@
1+#[derive(Default)]
2+pub struct Layout {
3+ corner_radii: [u32; 4],
4+}
new file mode 100644
@@ -0,0 +1,4 @@
1+#[derive(Default)]
2+pub struct Layout {
3+ corner_radii: [u32; 4],
4+}
added tests/cases/032-cosmic-theme-spacing/main.rs +60 -0
new file mode 100644
@@ -0,0 +1,60 @@
1+// cosmic-theme 1.0.0, the part of it that does not depend on `palette`.
2+//
3+// `corner.rs`, `spacing.rs` and `layout.rs` are the crate's own files,
4+// byte-for-byte. They are the spacing scale, corner radii and density model
5+// that a COSMIC-native UI needs in order to match the desktop. The rest of
6+// cosmic-theme is colour work built on `palette` (40,874 lines, plus a
7+// proc-macro crate), which is out of reach.
8+
9+mod corner;
10+mod layout;
11+mod spacing;
12+
13+use crate::corner::{CornerRadii, Roundness};
14+use crate::spacing::{Density, Spacing};
15+
16+fn show_spacing(tag: &str, s: Spacing) {
17+ println!(
18+ "{} {} {} {} {} {} {} {} {} {} {}",
19+ tag,
20+ s.space_none,
21+ s.space_xxxs,
22+ s.space_xxs,
23+ s.space_xs,
24+ s.space_s,
25+ s.space_m,
26+ s.space_l,
27+ s.space_xl,
28+ s.space_xxl,
29+ s.space_xxxl
30+ );
31+}
32+
33+fn show_corner(tag: &str, c: CornerRadii) {
34+ println!(
35+ "{} {} {} {} {} {} {}",
36+ tag, c.radius_0[0], c.radius_xs[0], c.radius_s[0], c.radius_m[0], c.radius_l[0], c.radius_xl[0]
37+ );
38+}
39+
40+fn main() {
41+ show_spacing("default", Spacing::default());
42+ show_spacing("compact", Spacing::from(Density::Compact));
43+ show_spacing("spacious", Spacing::from(Density::Spacious));
44+ show_spacing("standard", Spacing::from(Density::Standard));
45+
46+ // Density round-trips through Spacing.
47+ for d in [Density::Compact, Density::Spacious, Density::Standard] {
48+ let s: Spacing = Spacing::from(d);
49+ let back: Density = Density::from(s);
50+ println!("roundtrip {:?} -> {:?}", d, back);
51+ }
52+
53+ show_corner("default", CornerRadii::default());
54+ for r in [Roundness::Round, Roundness::SlightlyRound, Roundness::Square] {
55+ let c: CornerRadii = CornerRadii::from(r);
56+ let back: Roundness = Roundness::from(c);
57+ println!("corner {:?} -> {:?}", r, back);
58+ show_corner(" radii", CornerRadii::from(r));
59+ }
60+}
new file mode 100644
@@ -0,0 +1,60 @@
1+// cosmic-theme 1.0.0, the part of it that does not depend on `palette`.
2+//
3+// `corner.rs`, `spacing.rs` and `layout.rs` are the crate's own files,
4+// byte-for-byte. They are the spacing scale, corner radii and density model
5+// that a COSMIC-native UI needs in order to match the desktop. The rest of
6+// cosmic-theme is colour work built on `palette` (40,874 lines, plus a
7+// proc-macro crate), which is out of reach.
8+
9+mod corner;
10+mod layout;
11+mod spacing;
12+
13+use crate::corner::{CornerRadii, Roundness};
14+use crate::spacing::{Density, Spacing};
15+
16+fn show_spacing(tag: &str, s: Spacing) {
17+ println!(
18+ "{} {} {} {} {} {} {} {} {} {} {}",
19+ tag,
20+ s.space_none,
21+ s.space_xxxs,
22+ s.space_xxs,
23+ s.space_xs,
24+ s.space_s,
25+ s.space_m,
26+ s.space_l,
27+ s.space_xl,
28+ s.space_xxl,
29+ s.space_xxxl
30+ );
31+}
32+
33+fn show_corner(tag: &str, c: CornerRadii) {
34+ println!(
35+ "{} {} {} {} {} {} {}",
36+ tag, c.radius_0[0], c.radius_xs[0], c.radius_s[0], c.radius_m[0], c.radius_l[0], c.radius_xl[0]
37+ );
38+}
39+
40+fn main() {
41+ show_spacing("default", Spacing::default());
42+ show_spacing("compact", Spacing::from(Density::Compact));
43+ show_spacing("spacious", Spacing::from(Density::Spacious));
44+ show_spacing("standard", Spacing::from(Density::Standard));
45+
46+ // Density round-trips through Spacing.
47+ for d in [Density::Compact, Density::Spacious, Density::Standard] {
48+ let s: Spacing = Spacing::from(d);
49+ let back: Density = Density::from(s);
50+ println!("roundtrip {:?} -> {:?}", d, back);
51+ }
52+
53+ show_corner("default", CornerRadii::default());
54+ for r in [Roundness::Round, Roundness::SlightlyRound, Roundness::Square] {
55+ let c: CornerRadii = CornerRadii::from(r);
56+ let back: Roundness = Roundness::from(c);
57+ println!("corner {:?} -> {:?}", r, back);
58+ show_corner(" radii", CornerRadii::from(r));
59+ }
60+}
added tests/cases/032-cosmic-theme-spacing/spacing.rs +98 -0
new file mode 100644
@@ -0,0 +1,98 @@
1+
2+/// Spacing variables for the Cosmic theme
3+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
4+pub struct Spacing {
5+ /// No spacing
6+ pub space_none: u16,
7+ /// smallest spacing that can be non-zero
8+ pub space_xxxs: u16,
9+ /// extra extra small spacing
10+ pub space_xxs: u16,
11+ /// extra small spacing
12+ pub space_xs: u16,
13+ /// small spacing
14+ pub space_s: u16,
15+ /// medium spacing
16+ pub space_m: u16,
17+ /// large spacing
18+ pub space_l: u16,
19+ /// extra large spacing
20+ pub space_xl: u16,
21+ /// extra extra large spacing
22+ pub space_xxl: u16,
23+ /// largest possible spacing
24+ pub space_xxxl: u16,
25+}
26+
27+impl Default for Spacing {
28+ fn default() -> Self {
29+ Self {
30+ space_none: 0,
31+ space_xxxs: 4,
32+ space_xxs: 8,
33+ space_xs: 12,
34+ space_s: 16,
35+ space_m: 24,
36+ space_l: 32,
37+ space_xl: 48,
38+ space_xxl: 64,
39+ space_xxxl: 128,
40+ }
41+ }
42+}
43+
44+/// Density options for the Cosmic theme
45+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
46+pub enum Density {
47+ /// Lower padding/spacing of elements
48+ Compact,
49+ /// Standard padding/spacing of elements
50+ #[default]
51+ Standard,
52+ /// Higher padding/spacing of elements
53+ Spacious,
54+}
55+
56+impl From<Density> for Spacing {
57+ fn from(value: Density) -> Self {
58+ match value {
59+ Density::Compact => Spacing {
60+ space_none: 0,
61+ space_xxxs: 4,
62+ space_xxs: 4,
63+ space_xs: 8,
64+ space_s: 8,
65+ space_m: 16,
66+ space_l: 24,
67+ space_xl: 32,
68+ space_xxl: 48,
69+ space_xxxl: 64,
70+ },
71+ Density::Standard => Spacing::default(),
72+ Density::Spacious => Spacing {
73+ space_none: 4,
74+ space_xxxs: 8,
75+ space_xxs: 12,
76+ space_xs: 16,
77+ space_s: 24,
78+ space_m: 32,
79+ space_l: 48,
80+ space_xl: 64,
81+ space_xxl: 128,
82+ space_xxxl: 160,
83+ },
84+ }
85+ }
86+}
87+
88+impl From<Spacing> for Density {
89+ fn from(value: Spacing) -> Self {
90+ if value.space_m.saturating_sub(16) == 0 {
91+ Self::Compact
92+ } else if value.space_m.saturating_sub(24) == 0 {
93+ Self::Standard
94+ } else {
95+ Self::Spacious
96+ }
97+ }
98+}
new file mode 100644
@@ -0,0 +1,98 @@
1+
2+/// Spacing variables for the Cosmic theme
3+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
4+pub struct Spacing {
5+ /// No spacing
6+ pub space_none: u16,
7+ /// smallest spacing that can be non-zero
8+ pub space_xxxs: u16,
9+ /// extra extra small spacing
10+ pub space_xxs: u16,
11+ /// extra small spacing
12+ pub space_xs: u16,
13+ /// small spacing
14+ pub space_s: u16,
15+ /// medium spacing
16+ pub space_m: u16,
17+ /// large spacing
18+ pub space_l: u16,
19+ /// extra large spacing
20+ pub space_xl: u16,
21+ /// extra extra large spacing
22+ pub space_xxl: u16,
23+ /// largest possible spacing
24+ pub space_xxxl: u16,
25+}
26+
27+impl Default for Spacing {
28+ fn default() -> Self {
29+ Self {
30+ space_none: 0,
31+ space_xxxs: 4,
32+ space_xxs: 8,
33+ space_xs: 12,
34+ space_s: 16,
35+ space_m: 24,
36+ space_l: 32,
37+ space_xl: 48,
38+ space_xxl: 64,
39+ space_xxxl: 128,
40+ }
41+ }
42+}
43+
44+/// Density options for the Cosmic theme
45+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
46+pub enum Density {
47+ /// Lower padding/spacing of elements
48+ Compact,
49+ /// Standard padding/spacing of elements
50+ #[default]
51+ Standard,
52+ /// Higher padding/spacing of elements
53+ Spacious,
54+}
55+
56+impl From<Density> for Spacing {
57+ fn from(value: Density) -> Self {
58+ match value {
59+ Density::Compact => Spacing {
60+ space_none: 0,
61+ space_xxxs: 4,
62+ space_xxs: 4,
63+ space_xs: 8,
64+ space_s: 8,
65+ space_m: 16,
66+ space_l: 24,
67+ space_xl: 32,
68+ space_xxl: 48,
69+ space_xxxl: 64,
70+ },
71+ Density::Standard => Spacing::default(),
72+ Density::Spacious => Spacing {
73+ space_none: 4,
74+ space_xxxs: 8,
75+ space_xxs: 12,
76+ space_xs: 16,
77+ space_s: 24,
78+ space_m: 32,
79+ space_l: 48,
80+ space_xl: 64,
81+ space_xxl: 128,
82+ space_xxxl: 160,
83+ },
84+ }
85+ }
86+}
87+
88+impl From<Spacing> for Density {
89+ fn from(value: Spacing) -> Self {
90+ if value.space_m.saturating_sub(16) == 0 {
91+ Self::Compact
92+ } else if value.space_m.saturating_sub(24) == 0 {
93+ Self::Standard
94+ } else {
95+ Self::Spacious
96+ }
97+ }
98+}