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>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 | ||
| 214 | 214 | qualifier, then the current module, then what `use` brought into scope, then |
| 215 | 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 | 242 | ### Declaration order |
| 218 | 243 | |
| 219 | 244 | 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 | 304 | ### Still open |
| 280 | 305 | |
| 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. | |
| 286 | 308 | Lifetime parameters are *not* a rejection: they carry no runtime meaning |
| 287 | 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 | 314 | 8. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but |
| 289 | 315 | the exponent-form thresholds have only been checked at `1e21`. |
| 290 | 316 | 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 | 390 | 400 crates from the local registry (under 4,000 lines each), all their module |
| 365 | 391 | files passed together, first blocker recorded: |
| 366 | 392 | |
| 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. | |
| 382 | 414 | |
| 383 | 415 | So the ranking above is not a roadmap — it says which feature is most often |
| 384 | 416 | *first*, which is not the same as which feature finishes a crate. The only |
| 385 | 417 | honest way to add a crate is to pick it and clear its stack, as was done |
| 386 | 418 | twice. |
| 387 | 419 | |
| 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. | |
| 394 | 443 | |
| 395 | 444 | ## Proof of byte-identity for `base16ct` |
| 396 | 445 | |
| @@ -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, then | 214 | 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 order | 242 | ### Declaration order |
| 218 | 243 | ||
| 219 | Rust has no declaration-before-use rule and Nim does, so every proc is | 244 | 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 open | 304 | ### Still open |
| 280 | 305 | ||
| 281 | -6. `checked_*` and `saturating_*` are not mapped yet; they are currently | 306 | +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 meaning | 308 | 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`, but | 314 | 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: two | 316 | 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 module | 390 | 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 blockers | 403 | + |
| 378 | -**and moved the fully-working count by zero**. Every crate it unblocked | 404 | +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 are | 405 | +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 was | 406 | +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 often | 415 | 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 only | 416 | *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 done | 417 | 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 has | 421 | +(`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 in | 422 | +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 types | 423 | + |
| 392 | -(`type Item = ..` inside an `impl`) are the next after that and are mostly a | 424 | +### `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 | ||
| 30 | 30 | ## What works |
| 31 | 31 | |
| 32 | 32 | 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 | |
| 34 | 35 | exact widths, `as` casts, `if`/`while`/`loop`/`for`, `match` including binding |
| 35 | 36 | patterns, `Vec`/slices/arrays, type aliases, function-typed parameters |
| 36 | 37 | (`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 | 86 | incremental-write splits. It needed ten new features, and it caught a |
| 86 | 87 | regression that 33 passing cases had not. |
| 87 | 88 | |
| 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. | |
| 91 | 97 | |
| 92 | 98 | ## Tests |
| 93 | 99 | |
| @@ -30,7 +30,8 @@ rustnim src/lib.rs src/error.rs --cfg feature=alloc -o crate.nim | |||
| 30 | ## What works | 30 | ## 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 at | 33 | +`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 binding | 35 | exact widths, `as` casts, `if`/`while`/`loop`/`for`, `match` including binding |
| 35 | patterns, `Vec`/slices/arrays, type aliases, function-typed parameters | 36 | patterns, `Vec`/slices/arrays, type aliases, function-typed parameters |
| 36 | (`impl Fn(A) -> B`), multi-file input, `#[cfg]`, and `println!`/`format!` with | 37 | (`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 a | 86 | 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 founding | 89 | +`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 | ## Tests | 98 | ## Tests |
| 93 | 99 | ||
modified
src/lower.rs +380 -39 | @@ -150,6 +150,9 @@ impl Val { | ||
| 150 | 150 | struct Sig { |
| 151 | 151 | params: Vec<Nim>, |
| 152 | 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 | 158 | /// One variant of a Rust enum. |
| @@ -198,6 +201,12 @@ pub struct Lowerer { | ||
| 198 | 201 | cur_mod: String, |
| 199 | 202 | /// The type of the `impl` block being lowered, which `Self` names. |
| 200 | 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 | 210 | /// `use` brings a name into scope from another module. Flattening loses |
| 202 | 211 | /// the module structure, so the mapping is recorded and consulted when a |
| 203 | 212 | /// bare call is resolved. |
| @@ -268,6 +277,9 @@ impl Lowerer { | ||
| 268 | 277 | fns: HashMap::new(), |
| 269 | 278 | cur_mod: String::new(), |
| 270 | 279 | self_ty: None, |
| 280 | + impl_generics: Vec::new(), | |
| 281 | + fn_generics: Vec::new(), | |
| 282 | + type_generics: HashMap::new(), | |
| 271 | 283 | use_map: HashMap::new(), |
| 272 | 284 | structs: HashMap::new(), |
| 273 | 285 | enums: HashMap::new(), |
| @@ -499,13 +511,26 @@ impl Lowerer { | ||
| 499 | 511 | match item { |
| 500 | 512 | Item::Fn(f) => { |
| 501 | 513 | let (params, ret) = self.signature(&f.sig)?; |
| 514 | + let gen_names = Self::generics_of(&f.sig.generics); | |
| 502 | 515 | let name = f.sig.ident.to_string(); |
| 503 | 516 | let nim = self.fn_name(&self.cur_mod, &name); |
| 504 | 517 | self.forwards.push(self.head_of(&nim, &f.sig, None)?); |
| 505 | 518 | 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 | 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 | 534 | let mut fields = Vec::new(); |
| 510 | 535 | for (i, f) in s.fields.iter().enumerate() { |
| 511 | 536 | let name = match &f.ident { |
| @@ -542,9 +567,14 @@ impl Lowerer { | ||
| 542 | 567 | } |
| 543 | 568 | Item::Enum(e) => { |
| 544 | 569 | 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 | + )); | |
| 547 | 575 | } |
| 576 | + self.type_generics | |
| 577 | + .insert(name.clone(), Self::generics_of(&e.generics)); | |
| 548 | 578 | let mut variants = Vec::new(); |
| 549 | 579 | for v in &e.variants { |
| 550 | 580 | let vname = v.ident.to_string(); |
| @@ -582,10 +612,13 @@ impl Lowerer { | ||
| 582 | 612 | ); |
| 583 | 613 | } |
| 584 | 614 | Item::Impl(im) => { |
| 615 | + let outer_g = | |
| 616 | + std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics)); | |
| 585 | 617 | let self_ty = self.map_ty(&im.self_ty)?; |
| 586 | 618 | let outer_self = self.self_ty.replace(self_ty.clone()); |
| 587 | 619 | let r = self.collect_impl(im, &self_ty); |
| 588 | 620 | self.self_ty = outer_self; |
| 621 | + self.impl_generics = outer_g; | |
| 589 | 622 | return r; |
| 590 | 623 | } |
| 591 | 624 | _ => {} |
| @@ -644,6 +677,8 @@ impl Lowerer { | ||
| 644 | 677 | }; |
| 645 | 678 | let mname = m.sig.ident.to_string(); |
| 646 | 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 | 682 | let recv = if takes_self(&m.sig) { |
| 648 | 683 | params.insert(0, self_ty.clone()); |
| 649 | 684 | Some(self_ty.clone()) |
| @@ -653,7 +688,7 @@ impl Lowerer { | ||
| 653 | 688 | let nim = trait_method_name(&tyname, &tr, &mname); |
| 654 | 689 | self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?); |
| 655 | 690 | 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 | 692 | self.statics.insert((tyname.clone(), mname), nim); |
| 658 | 693 | } |
| 659 | 694 | return Ok(()); |
| @@ -661,6 +696,8 @@ impl Lowerer { | ||
| 661 | 696 | for it in &im.items { |
| 662 | 697 | if let syn::ImplItem::Fn(m) = it { |
| 663 | 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 | 701 | if takes_self(&m.sig) { |
| 665 | 702 | params.insert(0, self_ty.clone()); |
| 666 | 703 | } |
| @@ -669,7 +706,7 @@ impl Lowerer { | ||
| 669 | 706 | let head = self.head_of(&nim, &m.sig, recv.as_ref())?; |
| 670 | 707 | self.forwards.push(head); |
| 671 | 708 | 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 | 710 | self.statics |
| 674 | 711 | .insert((tyname.clone(), m.sig.ident.to_string()), nim); |
| 675 | 712 | } |
| @@ -788,6 +825,22 @@ impl Lowerer { | ||
| 788 | 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 | 844 | /// `Self` inside an `impl` block names the type being implemented. |
| 792 | 845 | fn subst_self(&self, t: Nim) -> Nim { |
| 793 | 846 | let Some(me) = &self.self_ty else { return t }; |
| @@ -841,6 +894,115 @@ impl Lowerer { | ||
| 841 | 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 | 1006 | /// The Nim name for a function, qualified by its module. |
| 845 | 1007 | fn fn_name(&self, module: &str, name: &str) -> String { |
| 846 | 1008 | if module.is_empty() { |
| @@ -882,6 +1044,15 @@ impl Lowerer { | ||
| 882 | 1044 | recv: Option<&Nim>, |
| 883 | 1045 | ) -> Result<String, String> { |
| 884 | 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(¶ms); | |
| 885 | 1056 | let mut parts = Vec::new(); |
| 886 | 1057 | if let Some(self_ty) = recv { |
| 887 | 1058 | let mutable = matches!( |
| @@ -910,9 +1081,15 @@ impl Lowerer { | ||
| 910 | 1081 | parts.push(format!("{}: {}", ident(&pname), t.render())); |
| 911 | 1082 | } |
| 912 | 1083 | Ok(if ret == Nim::Unit { |
| 913 | - format!("proc {}*({})", ident(name), parts.join(", ")) | |
| 1084 | + format!("proc {}*{}({})", ident(name), gens, parts.join(", ")) | |
| 914 | 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 | 1100 | return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident)); |
| 924 | 1101 | } |
| 925 | 1102 | // 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(_))) { | |
| 933 | 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 | 1109 | sig.ident |
| 936 | 1110 | )); |
| 937 | 1111 | } |
| @@ -997,7 +1171,8 @@ impl Lowerer { | ||
| 997 | 1171 | Item::Struct(s) => { |
| 998 | 1172 | let name = s.ident.to_string(); |
| 999 | 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 | 1176 | self.indent += 1; |
| 1002 | 1177 | if fields.is_empty() { |
| 1003 | 1178 | self.line("discard"); |
| @@ -1038,10 +1213,13 @@ impl Lowerer { | ||
| 1038 | 1213 | Ok(()) |
| 1039 | 1214 | } |
| 1040 | 1215 | Item::Impl(im) => { |
| 1216 | + let outer_g = | |
| 1217 | + std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics)); | |
| 1041 | 1218 | let self_ty = self.map_ty(&im.self_ty)?; |
| 1042 | 1219 | let outer = self.self_ty.replace(self_ty.clone()); |
| 1043 | 1220 | let r = self.impl_body(im, &self_ty); |
| 1044 | 1221 | self.self_ty = outer; |
| 1222 | + self.impl_generics = outer_g; | |
| 1045 | 1223 | r |
| 1046 | 1224 | } |
| 1047 | 1225 | // `use` and `extern crate` are resolution directives with no Nim |
| @@ -1149,7 +1327,16 @@ impl Lowerer { | ||
| 1149 | 1327 | |
| 1150 | 1328 | fn emit_enum(&mut self, def: &EnumDef) { |
| 1151 | 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 | 1340 | // Every variant is a unit variant, so a plain Nim enum is an exact |
| 1154 | 1341 | // fit: it compares, orders and `case`-checks like Rust's. |
| 1155 | 1342 | self.line(&format!("type {name}* = enum")); |
| @@ -1182,7 +1369,7 @@ impl Lowerer { | ||
| 1182 | 1369 | } |
| 1183 | 1370 | self.indent -= 1; |
| 1184 | 1371 | self.blank(); |
| 1185 | - self.line(&format!("{}* = object", name)); | |
| 1372 | + self.line(&format!("{}*{} = object", name, g)); | |
| 1186 | 1373 | self.indent += 1; |
| 1187 | 1374 | self.line(&format!("case kind*: {}Kind", name)); |
| 1188 | 1375 | for v in &def.variants { |
| @@ -1215,18 +1402,20 @@ impl Lowerer { | ||
| 1215 | 1402 | .collect(); |
| 1216 | 1403 | let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))]; |
| 1217 | 1404 | all.extend(inits); |
| 1405 | + let ret = format!("{}{}", name, g); | |
| 1218 | 1406 | self.line(&format!( |
| 1219 | - "proc {}*({}): {} = {}({})", | |
| 1407 | + "proc {}*{}({}): {} = {}({})", | |
| 1220 | 1408 | def.ctor_ident(&v.name), |
| 1409 | + g, | |
| 1221 | 1410 | args.join(", "), |
| 1222 | - name, | |
| 1223 | - name, | |
| 1411 | + ret, | |
| 1412 | + ret, | |
| 1224 | 1413 | all.join(", ") |
| 1225 | 1414 | )); |
| 1226 | 1415 | } |
| 1227 | 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 | 1419 | self.indent += 1; |
| 1231 | 1420 | self.line("case x.kind"); |
| 1232 | 1421 | for v in &def.variants { |
| @@ -1250,6 +1439,30 @@ impl Lowerer { | ||
| 1250 | 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 | 1466 | /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength` |
| 1254 | 1467 | /// to the enum that declares it. |
| 1255 | 1468 | fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> { |
| @@ -1416,10 +1629,24 @@ impl Lowerer { | ||
| 1416 | 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 | 1640 | let head = if ret == Nim::Unit { |
| 1420 | - format!("proc {}*({}) =", ident(name), rendered.join(", ")) | |
| 1641 | + format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", ")) | |
| 1421 | 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 | 1651 | self.line(&head); |
| 1425 | 1652 | self.indent += 1; |
| @@ -1458,6 +1685,7 @@ impl Lowerer { | ||
| 1458 | 1685 | |
| 1459 | 1686 | self.indent -= 1; |
| 1460 | 1687 | self.ret = outer_ret; |
| 1688 | + self.fn_generics = outer_fg; | |
| 1461 | 1689 | self.pop_scope(); |
| 1462 | 1690 | self.blank(); |
| 1463 | 1691 | Ok(()) |
| @@ -1669,7 +1897,13 @@ impl Lowerer { | ||
| 1669 | 1897 | // `var` parameter is wanted, so the binding has to be one. |
| 1670 | 1898 | let mutable = mutable || is_mut_borrow(&init.expr); |
| 1671 | 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 | 1907 | self.line(&line); |
| 1674 | 1908 | self.bind(&name, t); |
| 1675 | 1909 | Ok(()) |
| @@ -2436,7 +2670,13 @@ impl Lowerer { | ||
| 2436 | 2670 | let Some((def, v)) = self.resolve_variant(path) else { |
| 2437 | 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 | 2682 | fn arm_body(&mut self, body: &Expr) -> Result<(), String> { |
| @@ -2529,6 +2769,21 @@ impl Lowerer { | ||
| 2529 | 2769 | if name == "None" { |
| 2530 | 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 | 2787 | // A unit struct used as a value: `fmt::Error`, or a `struct S;` |
| 2533 | 2788 | // declared here. In Nim that is a constructor call. |
| 2534 | 2789 | if p.path.segments.len() > 1 { |
| @@ -2547,11 +2802,16 @@ impl Lowerer { | ||
| 2547 | 2802 | } |
| 2548 | 2803 | // A unit enum variant used as a value: `Error::InvalidLength`. |
| 2549 | 2804 | 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)) | |
| 2553 | 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 | 2817 | // A `for` binding that stands for an element of the container |
| @@ -2653,11 +2913,11 @@ impl Lowerer { | ||
| 2653 | 2913 | syn::Member::Unnamed(i) => format!("f{}", i.index), |
| 2654 | 2914 | }; |
| 2655 | 2915 | let t = match &base.ty { |
| 2656 | - Some(Nim::Named(s, _)) => self | |
| 2916 | + Some(bt @ Nim::Named(s, _)) => self | |
| 2657 | 2917 | .structs |
| 2658 | 2918 | .get(s) |
| 2659 | 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 | 2921 | _ => None, |
| 2662 | 2922 | }; |
| 2663 | 2923 | Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t)) |
| @@ -2732,7 +2992,16 @@ impl Lowerer { | ||
| 2732 | 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 | 3005 | let mut parts = Vec::new(); |
| 2737 | 3006 | for f in &s.fields { |
| 2738 | 3007 | let fname = match &f.member { |
| @@ -2747,9 +3016,28 @@ impl Lowerer { | ||
| 2747 | 3016 | let v = self.expr_at(&f.expr, want.as_ref())?; |
| 2748 | 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 | 3038 | Ok(Val::new( |
| 2751 | - format!("{}({})", ident(&name), parts.join(", ")), | |
| 2752 | - Some(Nim::Named(name, vec![])), | |
| 3039 | + format!("{}({})", ty.render(), parts.join(", ")), | |
| 3040 | + Some(ty), | |
| 2753 | 3041 | )) |
| 2754 | 3042 | } |
| 2755 | 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 | 3758 | // `u32::from(b)`: `From` between primitives is lossless by definition |
| 3450 | 3759 | // -- it is the widening direction only -- so a plain Nim conversion is |
| 3451 | 3760 | // exact. (The truncating direction is `as`, which is `cast`.) |
| @@ -3494,9 +3803,10 @@ impl Lowerer { | ||
| 3494 | 3803 | |
| 3495 | 3804 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. |
| 3496 | 3805 | if let Some((def, v)) = self.resolve_variant(&p.path) { |
| 3806 | + let (ty, _) = self.variant_type(&def, expect)?; | |
| 3497 | 3807 | return Ok(Val::new( |
| 3498 | 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 | 3830 | q |
| 3521 | 3831 | }; |
| 3522 | 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 | 3835 | let nim = self |
| 3525 | 3836 | .statics |
| 3526 | 3837 | .get(&(q.clone(), name.clone())) |
| @@ -3529,7 +3840,11 @@ impl Lowerer { | ||
| 3529 | 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 | 3848 | if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) { |
| 3534 | 3849 | return Err(format!( |
| 3535 | 3850 | "call to unknown function `{name}`; only functions defined in \ |
| @@ -3822,6 +4137,30 @@ impl Lowerer { | ||
| 3822 | 4137 | }; |
| 3823 | 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 | 4164 | "abs" => (format!("abs({})", recv.code), rt.clone()), |
| 3826 | 4165 | "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), |
| 3827 | 4166 | "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), |
| @@ -3853,10 +4192,12 @@ impl Lowerer { | ||
| 3853 | 4192 | // A method defined in this file via `impl`, found by the |
| 3854 | 4193 | // receiver's type rather than by name alone. |
| 3855 | 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 | 4197 | let sig = key |
| 3857 | 4198 | .as_ref() |
| 3858 | 4199 | .and_then(|k| self.methods.get(k)) |
| 3859 | - .map(|s| s.ret.clone()); | |
| 4200 | + .map(|s| Self::instantiate(s, &arg_tys)); | |
| 3860 | 4201 | if let Some(ret) = sig { |
| 3861 | 4202 | // Use the name the proc was actually emitted under: an |
| 3862 | 4203 | // 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 loses | 210 | /// `use` brings a name into scope from another module. Flattening loses |
| 202 | /// the module structure, so the mapping is recorded and consulted when a | 211 | /// 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.fns | 518 | 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.methods | 690 | 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.methods | 708 | 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.statics | 710 | 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(¶ms); | ||
| 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, so | 1102 | // 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 | 1103 | + // 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.ident | 1109 | 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 | r | 1223 | r |
| 1046 | } | 1224 | } |
| 1047 | // `use` and `extern crate` are resolution directives with no Nim | 1225 | // `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 exact | 1340 | // 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 container | 2817 | // 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, _)) => self | 2916 | + Some(bt @ Nim::Named(s, _)) => self |
| 2657 | .structs | 2917 | .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 definition | 3758 | // `u32::from(b)`: `From` between primitives is lossless by definition |
| 3450 | // -- it is the widening direction only -- so a plain Nim conversion is | 3759 | // -- 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 | q | 3830 | 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 = self | 3835 | let nim = self |
| 3525 | .statics | 3836 | .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 the | 4192 | // 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 = key | 4197 | 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: an | 4202 | // Use the name the proc was actually emitted under: an |
| 3862 | // inherent method is qualified by its module, a trait | 4203 | // 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 = | ||
| 57 | 57 | if not r.ok: rsPanic("called `Result::unwrap()` on an `Err` value") |
| 58 | 58 | 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 | 121 | # Display / Debug. |
| 62 | 122 | # |
| @@ -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.val | 58 | 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 | +} | ||