From 5859b6dd59708c26a7671790f8ca6a0b6c2cc705 Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 11 Apr 2026 01:49:07 +0000 Subject: [PATCH 1/6] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/linksplatform/Numbers/issues/142 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..83e57dd --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-04-11T01:49:06.963Z for PR creation at branch issue-142-bd959714447f for issue https://github.com/linksplatform/Numbers/issues/142 \ No newline at end of file From d513ed4631723b2776116512040725abd7814cd6 Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 11 Apr 2026 01:55:19 +0000 Subject: [PATCH 2/6] feat: upgrade to Rust edition 2024 and audit codebase for best practices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate to Rust edition 2024 (stable since 1.85) with zero code changes required. Fix documentation inaccuracies and remove stale TODO comments. Changes: - Upgrade edition 2021 → 2024, MSRV 1.70 → 1.85 - Fix CONTRIBUTING.md: "nightly toolchain" → "stable toolchain" - Fix rust/README.md: install version 0.4 → 0.5 - Remove stale TODO comments from imp.rs - Regenerate Cargo.lock to match version 0.5.0 - Add case study for issue #142 audit findings - Add changelog fragment Closes #142 Co-Authored-By: Claude Opus 4.6 --- CONTRIBUTING.md | 2 +- .../20260411_000000_edition_2024_audit.md | 16 +++ docs/case-studies/issue-142/README.md | 129 ++++++++++++++++++ rust/Cargo.lock | 2 +- rust/Cargo.toml | 4 +- rust/README.md | 2 +- rust/src/imp.rs | 2 - 7 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 changelog.d/20260411_000000_edition_2024_audit.md create mode 100644 docs/case-studies/issue-142/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e028df..7f802e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ Thank you for your interest in contributing! This document provides guidelines a curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` - This project uses a specific nightly toolchain. It will be automatically selected when you run cargo commands due to `rust/rust-toolchain.toml`. + This project uses the stable toolchain. It will be automatically selected when you run cargo commands due to `rust/rust-toolchain.toml`. 3. **Install development tools** diff --git a/changelog.d/20260411_000000_edition_2024_audit.md b/changelog.d/20260411_000000_edition_2024_audit.md new file mode 100644 index 0000000..5ba8ee9 --- /dev/null +++ b/changelog.d/20260411_000000_edition_2024_audit.md @@ -0,0 +1,16 @@ +--- +bump: minor +--- + +### Changed +- Upgrade Rust edition from 2021 to 2024 (stable since Rust 1.85) +- Bump MSRV from 1.70 to 1.85 (required for edition 2024) + +### Fixed +- Fix CONTRIBUTING.md incorrectly stating "nightly toolchain" (project uses stable) +- Fix README.md showing outdated install version `0.4` instead of `0.5` +- Remove stale TODO comments from source code +- Regenerate Cargo.lock to match current version (0.5.0) + +### Added +- Add case study documentation for issue #142 audit findings diff --git a/docs/case-studies/issue-142/README.md b/docs/case-studies/issue-142/README.md new file mode 100644 index 0000000..e763206 --- /dev/null +++ b/docs/case-studies/issue-142/README.md @@ -0,0 +1,129 @@ +# Case Study: Issue #142 — Rust Best Practices Audit and Edition 2024 Migration + +## Overview + +- **Issue:** [#142](https://github.com/linksplatform/Numbers/issues/142) +- **PR:** [#143](https://github.com/linksplatform/Numbers/pull/143) +- **Date:** 2026-04-11 +- **Status:** Audited and fixed. + +--- + +## Issue Requirements + +The issue requested a comprehensive audit of the Rust codebase: + +1. **Verify no non-stable Rust features are used** — ensure the crate compiles on stable Rust without any `#![feature(...)]` flags. +2. **Use latest versions of dependencies** — check all dependencies are at their latest stable versions. +3. **Use latest stable version of Rust** — update the edition and MSRV to the latest stable. +4. **Ensure highest possible code quality** — apply latest Rust best practices. +5. **Sync documentation with code** — verify docs fully describe all features. +6. **Compile case study data** — document findings in `docs/case-studies/issue-142/`. + +--- + +## Audit Findings + +### 1. Non-Stable Features + +**Finding:** The crate uses **no non-stable features**. No `#![feature(...)]` attributes are present anywhere in the source code. The `rust-toolchain.toml` correctly specifies `channel = "stable"`. + +**Verdict:** ✅ Compliant — no action needed. + +### 2. Dependencies + +**Finding:** The only dependency is `num-traits`, which was already at the latest version (`0.2.19`). The `num-traits` crate is the de facto standard for generic numeric programming in Rust, maintained by the `rust-num` project. No alternative crate offers equivalent functionality with better maintenance or API design. + +**Verdict:** ✅ Already up-to-date — no action needed. + +### 3. Rust Edition and MSRV + +**Finding:** The crate used `edition = "2021"` and `rust-version = "1.70"`. + +- **Rust Edition 2024** was stabilized on 2025-02-20 with Rust 1.85.0 ([announcement](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/)). +- Edition 2024 is now widely adopted and brings improvements to lifetime capture rules, match ergonomics, and macro fragment specifiers. +- The crate migrated cleanly to Edition 2024 with zero code changes required. + +**Action taken:** Updated `edition` to `"2024"` and `rust-version` to `"1.85"`. + +### 4. Code Quality + +**Finding:** The code quality was already high: + +- **Clippy:** Zero warnings with all targets and features enabled. +- **Formatting:** Fully compliant with `rustfmt`. +- **Tests:** 67 unit tests + 5 doc tests, all passing. 100% code coverage. +- **Architecture:** Clean trait design with blanket implementations and zero-cost abstractions. + +**Minor issues found and fixed:** +- **Stale TODO comments:** Two TODO comments in `imp.rs` (lines 117 and 151) referenced work that was either already addressed or no longer relevant. Removed. +- **Cargo.lock version mismatch:** Lock file showed version `0.4.0` while `Cargo.toml` was at `0.5.0`. Regenerated. + +### 5. Documentation Sync + +**Findings and fixes:** + +| Document | Issue | Fix | +|----------|-------|-----| +| `rust/README.md` | Installation example showed `platform-num = "0.4"` | Updated to `"0.5"` | +| `CONTRIBUTING.md` | Stated project uses "a specific nightly toolchain" | Corrected to "the stable toolchain" | +| `rust/README.md` | Trait documentation | Already in sync with code — no changes needed | +| Doc comments in `imp.rs` | All five traits have `///` docs with examples | Already in sync — no changes needed | + +--- + +## Solution Plan + +### Approach: Minimal, Targeted Changes + +Since the codebase was already in good shape, the approach was to make only the changes that genuinely improve quality: + +1. **Edition upgrade (2021 → 2024):** The most impactful change. Adopts the latest stable edition, bringing the crate in line with modern Rust ecosystem standards. Zero code changes required — the migration was seamless. + +2. **MSRV bump (1.70 → 1.85):** Required by Edition 2024 (minimum Rust 1.85). This is reasonable because the MSRV-aware resolver (stabilized in Rust 1.84) ensures downstream users on older toolchains automatically get compatible older versions of this crate. + +3. **Documentation corrections:** Fixed factual inaccuracies (nightly vs stable, version 0.4 vs 0.5) that could mislead contributors and users. + +4. **TODO cleanup:** Removed stale TODO comments that added noise without value. + +5. **Cargo.lock regeneration:** Ensured lock file reflects the actual published version. + +--- + +## Research: Related Tools and Libraries + +### Alternatives to `num-traits` + +| Crate | Description | Assessment | +|-------|-------------|------------| +| [`num-traits`](https://crates.io/crates/num-traits) (current) | Standard numeric traits for Rust | Best choice — 667M+ downloads, stable API, active maintenance | +| [`num`](https://crates.io/crates/num) | Full numeric library (includes num-traits) | Heavier — unnecessary for trait-only usage | +| [`num-integer`](https://crates.io/crates/num-integer) | Integer-specific traits | Too narrow — doesn't provide PrimInt, Signed, etc. | + +**Conclusion:** `num-traits` remains the optimal dependency for this crate's use case. + +### Rust Edition 2024 Key Changes + +The following Edition 2024 changes were evaluated for impact on this crate: + +| Change | Impact on `platform-num` | +|--------|--------------------------| +| RPIT lifetime capture | None — crate doesn't use `impl Trait` in return position | +| `if let` temporary scope | None — no `if let` in trait definitions | +| Match ergonomics refinements | None — no pattern matching in trait code | +| `expr` macro fragment changes | None — macros use `$T:ty`, not `$e:expr` | +| Prelude additions (Future, IntoFuture) | None — crate is synchronous | + +**Result:** Clean migration with zero code changes. + +--- + +## Verification + +All quality checks pass after changes: + +- `cargo fmt --check` — ✅ clean +- `cargo clippy --all-targets --all-features` — ✅ zero warnings +- `cargo test --all-features --verbose` — ✅ 67 unit tests + 5 doc tests pass +- `cargo test --doc --verbose` — ✅ all doc tests pass +- Edition 2024 compilation — ✅ no migration issues diff --git a/rust/Cargo.lock b/rust/Cargo.lock index f257234..3926fad 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "platform-num" -version = "0.4.0" +version = "0.5.0" dependencies = [ "num-traits", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 76d9bfc..f884b46 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "platform-num" version = "0.5.0" -edition = "2021" -rust-version = "1.70" +edition = "2024" +rust-version = "1.85" authors = ["uselesssgoddess", "Linksplatform Team "] license = "Unlicense" repository = "https://github.com/linksplatform/Numbers" diff --git a/rust/README.md b/rust/README.md index 3adfa1e..f951c45 100644 --- a/rust/README.md +++ b/rust/README.md @@ -36,7 +36,7 @@ Add to your `Cargo.toml`: ```toml [dependencies] -platform-num = "0.4" +platform-num = "0.5" ``` ## Usage diff --git a/rust/src/imp.rs b/rust/src/imp.rs index 73198f4..ae44c90 100644 --- a/rust/src/imp.rs +++ b/rust/src/imp.rs @@ -114,7 +114,6 @@ macro_rules! max_value_impl { }; } -// TODO: Create macro foreach max_value_impl!(i8); max_value_impl!(u8); max_value_impl!(i16); @@ -148,7 +147,6 @@ max_value_impl!(usize); /// let link = create_link(1u64, 2u64); /// assert_eq!(link, (1u64, 2u64)); /// ``` -// TODO: Not use alias - IDEs does not support it #[rustfmt::skip] pub trait LinkReference: Number From 44b02b497cc68ae245b954db166587f88bfe3f46 Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 11 Apr 2026 02:02:43 +0000 Subject: [PATCH 3/6] Revert "Initial commit with task details" This reverts commit 5859b6dd59708c26a7671790f8ca6a0b6c2cc705. --- .gitkeep | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index 83e57dd..0000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-04-11T01:49:06.963Z for PR creation at branch issue-142-bd959714447f for issue https://github.com/linksplatform/Numbers/issues/142 \ No newline at end of file From 3418fd72929b2ef6a3c31a632a0e2d38163dc05b Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 11 Apr 2026 02:37:16 +0000 Subject: [PATCH 4/6] feat: implement TODO items, move tests to tests/, add rustdoc CI - Implement `for_each_integer_type!` macro (resolves TODO "Create macro foreach") - Document IDE alias design decision in LinkReference (resolves TODO "Not use alias") - Move all tests from src/imp.rs to tests/traits.rs (integration tests) - Add 8 new tests (ToPrimitive, PrimInt ops, FromPrimitive, Send+Sync, Display, Hash) - Add crate-level documentation in lib.rs with trait summary table - Add deploy-rust-docs.yml workflow for automated docs on GitHub Pages - Update case study with new findings Co-Authored-By: Claude Opus 4.6 --- .github/workflows/deploy-rust-docs.yml | 34 ++ docs/case-studies/issue-142/README.md | 52 ++- rust/src/imp.rs | 540 ++-------------------- rust/src/lib.rs | 31 ++ rust/tests/traits.rs | 591 +++++++++++++++++++++++++ 5 files changed, 723 insertions(+), 525 deletions(-) create mode 100644 .github/workflows/deploy-rust-docs.yml create mode 100644 rust/tests/traits.rs diff --git a/.github/workflows/deploy-rust-docs.yml b/.github/workflows/deploy-rust-docs.yml new file mode 100644 index 0000000..96b4615 --- /dev/null +++ b/.github/workflows/deploy-rust-docs.yml @@ -0,0 +1,34 @@ +name: Deploy Rust Docs + +on: + push: + branches: main + paths: + - 'rust/**' + - '.github/workflows/deploy-rust-docs.yml' + workflow_dispatch: + +permissions: + contents: write + +jobs: + deploy-docs: + name: Build and Deploy Rust Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build documentation + working-directory: rust + run: cargo doc --no-deps + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: rust/target/doc + destination_dir: rust + keep_files: true diff --git a/docs/case-studies/issue-142/README.md b/docs/case-studies/issue-142/README.md index e763206..7e75777 100644 --- a/docs/case-studies/issue-142/README.md +++ b/docs/case-studies/issue-142/README.md @@ -52,11 +52,15 @@ The issue requested a comprehensive audit of the Rust codebase: - **Clippy:** Zero warnings with all targets and features enabled. - **Formatting:** Fully compliant with `rustfmt`. -- **Tests:** 67 unit tests + 5 doc tests, all passing. 100% code coverage. +- **Tests:** 75 integration tests + 6 doc tests, all passing. - **Architecture:** Clean trait design with blanket implementations and zero-cost abstractions. -**Minor issues found and fixed:** -- **Stale TODO comments:** Two TODO comments in `imp.rs` (lines 117 and 151) referenced work that was either already addressed or no longer relevant. Removed. +**Improvements made:** + +- **Implemented `for_each_integer_type!` macro:** The original TODO comment `// TODO: Create macro foreach` requested a macro to DRY up repetitive per-type macro invocations. Implemented the `for_each_integer_type!` helper macro that invokes a given macro for all 12 primitive integer types. +- **Documented IDE alias design decision:** The original TODO comment `// TODO: Not use alias - IDEs does not support it` was about the `LinkReference` trait listing supertraits explicitly. Added a `# Design note` in the doc comment explaining why aliases are not used (IDE compatibility). +- **Moved tests to `tests/` directory:** All unit tests were in `src/imp.rs`. Moved them to `rust/tests/traits.rs` as proper integration tests, following Rust best practices for separation of tests from source code. +- **Added new tests:** Added 8 additional integration tests covering `ToPrimitive`, `PrimInt` operations, `FromPrimitive`, `Send + Sync`, `Display`, `Hash`, and overflow scenarios. Total: 75 integration tests + 6 doc tests = 81 tests. - **Cargo.lock version mismatch:** Lock file showed version `0.4.0` while `Cargo.toml` was at `0.5.0`. Regenerated. ### 5. Documentation Sync @@ -67,26 +71,38 @@ The issue requested a comprehensive audit of the Rust codebase: |----------|-------|-----| | `rust/README.md` | Installation example showed `platform-num = "0.4"` | Updated to `"0.5"` | | `CONTRIBUTING.md` | Stated project uses "a specific nightly toolchain" | Corrected to "the stable toolchain" | -| `rust/README.md` | Trait documentation | Already in sync with code — no changes needed | -| Doc comments in `imp.rs` | All five traits have `///` docs with examples | Already in sync — no changes needed | +| `rust/src/lib.rs` | No crate-level documentation | Added comprehensive module-level docs with trait summary table and example | +| `rust/src/imp.rs` | All five traits have `///` docs with examples | Already in sync — enhanced `LinkReference` with design note | + +### 6. Automated Documentation Generation + +**Finding:** The C# part of the project already has automated docs via `docfx`, deployed to GitHub Pages at `csharp/` subdirectory on the `gh-pages` branch. + +**Action taken:** Added `.github/workflows/deploy-rust-docs.yml` workflow that: +- Triggers on push to `main` when Rust files change +- Runs `cargo doc --no-deps` to generate HTML documentation +- Deploys to `rust/` subdirectory on the `gh-pages` branch +- Uses `peaceiris/actions-gh-pages@v4` (same approach as the C# docs) + +This makes Rust documentation available alongside C# documentation on the project's GitHub Pages site. --- ## Solution Plan -### Approach: Minimal, Targeted Changes +### Approach: Targeted Improvements -Since the codebase was already in good shape, the approach was to make only the changes that genuinely improve quality: +1. **Edition upgrade (2021 → 2024):** Adopts the latest stable edition, bringing the crate in line with modern Rust ecosystem standards. Zero code changes required. -1. **Edition upgrade (2021 → 2024):** The most impactful change. Adopts the latest stable edition, bringing the crate in line with modern Rust ecosystem standards. Zero code changes required — the migration was seamless. +2. **MSRV bump (1.70 → 1.85):** Required by Edition 2024. The MSRV-aware resolver (stabilized in Rust 1.84) ensures downstream users on older toolchains automatically get compatible older versions. -2. **MSRV bump (1.70 → 1.85):** Required by Edition 2024 (minimum Rust 1.85). This is reasonable because the MSRV-aware resolver (stabilized in Rust 1.84) ensures downstream users on older toolchains automatically get compatible older versions of this crate. +3. **Documentation improvements:** Fixed factual inaccuracies, added crate-level docs, enhanced trait documentation with design notes. -3. **Documentation corrections:** Fixed factual inaccuracies (nightly vs stable, version 0.4 vs 0.5) that could mislead contributors and users. +4. **TODO resolution:** Instead of removing TODOs, implemented the requested `for_each_integer_type!` macro and documented the IDE alias design decision. -4. **TODO cleanup:** Removed stale TODO comments that added noise without value. +5. **Test restructuring:** Moved tests from `src/` to `tests/` directory and added new test coverage. -5. **Cargo.lock regeneration:** Ensured lock file reflects the actual published version. +6. **Automated docs deployment:** Added CI workflow for Rust documentation generation and GitHub Pages deployment. --- @@ -116,6 +132,14 @@ The following Edition 2024 changes were evaluated for impact on this crate: **Result:** Clean migration with zero code changes. +### Rust Documentation Generation + +For Rust projects, `cargo doc` is the standard documentation generator (equivalent to C#'s `docfx`). It: +- Parses `///` and `//!` doc comments using Markdown +- Generates cross-linked HTML with search +- Supports code examples that are compiled and tested (`cargo test --doc`) +- Is built into the Rust toolchain — no additional dependencies needed + --- ## Verification @@ -124,6 +148,6 @@ All quality checks pass after changes: - `cargo fmt --check` — ✅ clean - `cargo clippy --all-targets --all-features` — ✅ zero warnings -- `cargo test --all-features --verbose` — ✅ 67 unit tests + 5 doc tests pass -- `cargo test --doc --verbose` — ✅ all doc tests pass +- `cargo test --all-features` — ✅ 75 integration tests + 6 doc tests pass +- `cargo doc --no-deps` — ✅ documentation generates successfully - Edition 2024 compilation — ✅ no migration issues diff --git a/rust/src/imp.rs b/rust/src/imp.rs index ae44c90..dbc5e74 100644 --- a/rust/src/imp.rs +++ b/rust/src/imp.rs @@ -106,6 +106,28 @@ pub trait MaxValue { const MAX: Self; } +/// Applies a macro to each primitive integer type. +/// +/// This helper macro invokes `$macro!(type)` for every primitive integer type: +/// `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, +/// `isize`, `usize`. +macro_rules! for_each_integer_type { + ($macro:ident) => { + $macro!(i8); + $macro!(u8); + $macro!(i16); + $macro!(u16); + $macro!(i32); + $macro!(u32); + $macro!(i64); + $macro!(u64); + $macro!(i128); + $macro!(u128); + $macro!(isize); + $macro!(usize); + }; +} + macro_rules! max_value_impl { ($T:ty) => { impl MaxValue for $T { @@ -114,18 +136,7 @@ macro_rules! max_value_impl { }; } -max_value_impl!(i8); -max_value_impl!(u8); -max_value_impl!(i16); -max_value_impl!(u16); -max_value_impl!(i32); -max_value_impl!(u32); -max_value_impl!(i64); -max_value_impl!(u64); -max_value_impl!(i128); -max_value_impl!(u128); -max_value_impl!(isize); -max_value_impl!(usize); +for_each_integer_type!(max_value_impl); /// A composite trait for types that can be used as link identifiers. /// @@ -135,6 +146,12 @@ max_value_impl!(usize); /// /// Implemented for `u8`, `u16`, `u32`, `u64`, and `usize`. /// +/// # Design note +/// +/// Supertraits are listed explicitly rather than via type aliases, +/// because some IDEs do not fully resolve trait aliases for +/// autocompletion and go-to-definition. +/// /// # Examples /// /// ``` @@ -175,502 +192,3 @@ impl< + Sync + 'static, > LinkReference for All {} - -#[cfg(test)] -mod tests { - use super::*; - - // ========================================== - // Tests for Number trait - // ========================================== - - #[test] - fn test_number_trait_for_i8() { - fn assert_number(_val: T) {} - assert_number(0i8); - } - - #[test] - fn test_number_trait_for_u8() { - fn assert_number(_val: T) {} - assert_number(0u8); - } - - #[test] - fn test_number_trait_for_i16() { - fn assert_number(_val: T) {} - assert_number(0i16); - } - - #[test] - fn test_number_trait_for_u16() { - fn assert_number(_val: T) {} - assert_number(0u16); - } - - #[test] - fn test_number_trait_for_i32() { - fn assert_number(_val: T) {} - assert_number(0i32); - } - - #[test] - fn test_number_trait_for_u32() { - fn assert_number(_val: T) {} - assert_number(0u32); - } - - #[test] - fn test_number_trait_for_i64() { - fn assert_number(_val: T) {} - assert_number(0i64); - } - - #[test] - fn test_number_trait_for_u64() { - fn assert_number(_val: T) {} - assert_number(0u64); - } - - #[test] - fn test_number_trait_for_i128() { - fn assert_number(_val: T) {} - assert_number(0i128); - } - - #[test] - fn test_number_trait_for_u128() { - fn assert_number(_val: T) {} - assert_number(0u128); - } - - #[test] - fn test_number_trait_for_isize() { - fn assert_number(_val: T) {} - assert_number(0isize); - } - - #[test] - fn test_number_trait_for_usize() { - fn assert_number(_val: T) {} - assert_number(0usize); - } - - // ========================================== - // Tests for SignedNumber trait - // ========================================== - - #[test] - fn test_signed_number_trait_for_i8() { - fn assert_signed_number(_val: T) {} - assert_signed_number(0i8); - } - - #[test] - fn test_signed_number_trait_for_i16() { - fn assert_signed_number(_val: T) {} - assert_signed_number(0i16); - } - - #[test] - fn test_signed_number_trait_for_i32() { - fn assert_signed_number(_val: T) {} - assert_signed_number(0i32); - } - - #[test] - fn test_signed_number_trait_for_i64() { - fn assert_signed_number(_val: T) {} - assert_signed_number(0i64); - } - - #[test] - fn test_signed_number_trait_for_i128() { - fn assert_signed_number(_val: T) {} - assert_signed_number(0i128); - } - - #[test] - fn test_signed_number_trait_for_isize() { - fn assert_signed_number(_val: T) {} - assert_signed_number(0isize); - } - - // ========================================== - // Tests for ToSigned trait - // ========================================== - - #[test] - fn test_to_signed_i8() { - let val: i8 = 42; - let signed: i8 = val.to_signed(); - assert_eq!(signed, 42i8); - } - - #[test] - fn test_to_signed_u8() { - let val: u8 = 42; - let signed: i8 = val.to_signed(); - assert_eq!(signed, 42i8); - } - - #[test] - fn test_to_signed_i16() { - let val: i16 = 1000; - let signed: i16 = val.to_signed(); - assert_eq!(signed, 1000i16); - } - - #[test] - fn test_to_signed_u16() { - let val: u16 = 1000; - let signed: i16 = val.to_signed(); - assert_eq!(signed, 1000i16); - } - - #[test] - fn test_to_signed_i32() { - let val: i32 = 100000; - let signed: i32 = val.to_signed(); - assert_eq!(signed, 100000i32); - } - - #[test] - fn test_to_signed_u32() { - let val: u32 = 100000; - let signed: i32 = val.to_signed(); - assert_eq!(signed, 100000i32); - } - - #[test] - fn test_to_signed_i64() { - let val: i64 = 1000000000; - let signed: i64 = val.to_signed(); - assert_eq!(signed, 1000000000i64); - } - - #[test] - fn test_to_signed_u64() { - let val: u64 = 1000000000; - let signed: i64 = val.to_signed(); - assert_eq!(signed, 1000000000i64); - } - - #[test] - fn test_to_signed_i128() { - let val: i128 = 1000000000000000; - let signed: i128 = val.to_signed(); - assert_eq!(signed, 1000000000000000i128); - } - - #[test] - fn test_to_signed_u128() { - let val: u128 = 1000000000000000; - let signed: i128 = val.to_signed(); - assert_eq!(signed, 1000000000000000i128); - } - - #[test] - fn test_to_signed_isize() { - let val: isize = 12345; - let signed: isize = val.to_signed(); - assert_eq!(signed, 12345isize); - } - - #[test] - fn test_to_signed_usize() { - let val: usize = 12345; - let signed: isize = val.to_signed(); - assert_eq!(signed, 12345isize); - } - - #[test] - fn test_to_signed_type_alias_i8() { - fn check>(_val: T) {} - check(0i8); - check(0u8); - } - - #[test] - fn test_to_signed_type_alias_i16() { - fn check>(_val: T) {} - check(0i16); - check(0u16); - } - - #[test] - fn test_to_signed_type_alias_i32() { - fn check>(_val: T) {} - check(0i32); - check(0u32); - } - - #[test] - fn test_to_signed_type_alias_i64() { - fn check>(_val: T) {} - check(0i64); - check(0u64); - } - - #[test] - fn test_to_signed_type_alias_i128() { - fn check>(_val: T) {} - check(0i128); - check(0u128); - } - - #[test] - fn test_to_signed_type_alias_isize() { - fn check>(_val: T) {} - check(0isize); - check(0usize); - } - - // ========================================== - // Tests for MaxValue trait - // ========================================== - - #[test] - fn test_max_value_i8() { - assert_eq!(i8::MAX, ::MAX); - } - - #[test] - fn test_max_value_u8() { - assert_eq!(u8::MAX, ::MAX); - } - - #[test] - fn test_max_value_i16() { - assert_eq!(i16::MAX, ::MAX); - } - - #[test] - fn test_max_value_u16() { - assert_eq!(u16::MAX, ::MAX); - } - - #[test] - fn test_max_value_i32() { - assert_eq!(i32::MAX, ::MAX); - } - - #[test] - fn test_max_value_u32() { - assert_eq!(u32::MAX, ::MAX); - } - - #[test] - fn test_max_value_i64() { - assert_eq!(i64::MAX, ::MAX); - } - - #[test] - fn test_max_value_u64() { - assert_eq!(u64::MAX, ::MAX); - } - - #[test] - fn test_max_value_i128() { - assert_eq!(i128::MAX, ::MAX); - } - - #[test] - fn test_max_value_u128() { - assert_eq!(u128::MAX, ::MAX); - } - - #[test] - fn test_max_value_isize() { - assert_eq!(isize::MAX, ::MAX); - } - - #[test] - fn test_max_value_usize() { - assert_eq!(usize::MAX, ::MAX); - } - - // ========================================== - // Tests for LinkReference trait - // ========================================== - - #[test] - fn test_link_reference_for_u8() { - fn assert_link_reference(_val: T) {} - assert_link_reference(0u8); - } - - #[test] - fn test_link_reference_for_u16() { - fn assert_link_reference(_val: T) {} - assert_link_reference(0u16); - } - - #[test] - fn test_link_reference_for_u32() { - fn assert_link_reference(_val: T) {} - assert_link_reference(0u32); - } - - #[test] - fn test_link_reference_for_u64() { - fn assert_link_reference(_val: T) {} - assert_link_reference(0u64); - } - - #[test] - fn test_link_reference_for_usize() { - fn assert_link_reference(_val: T) {} - assert_link_reference(0usize); - } - - // ========================================== - // Edge case tests for ToSigned - // ========================================== - - #[test] - fn test_to_signed_u8_max() { - let val: u8 = u8::MAX; - let signed: i8 = val.to_signed(); - assert_eq!(signed, -1i8); // Wrapping behavior - } - - #[test] - fn test_to_signed_u16_max() { - let val: u16 = u16::MAX; - let signed: i16 = val.to_signed(); - assert_eq!(signed, -1i16); // Wrapping behavior - } - - #[test] - fn test_to_signed_u32_max() { - let val: u32 = u32::MAX; - let signed: i32 = val.to_signed(); - assert_eq!(signed, -1i32); // Wrapping behavior - } - - #[test] - fn test_to_signed_u64_max() { - let val: u64 = u64::MAX; - let signed: i64 = val.to_signed(); - assert_eq!(signed, -1i64); // Wrapping behavior - } - - #[test] - fn test_to_signed_u128_max() { - let val: u128 = u128::MAX; - let signed: i128 = val.to_signed(); - assert_eq!(signed, -1i128); // Wrapping behavior - } - - #[test] - fn test_to_signed_usize_max() { - let val: usize = usize::MAX; - let signed: isize = val.to_signed(); - assert_eq!(signed, -1isize); // Wrapping behavior - } - - #[test] - fn test_to_signed_zero() { - assert_eq!(0u8.to_signed(), 0i8); - assert_eq!(0u16.to_signed(), 0i16); - assert_eq!(0u32.to_signed(), 0i32); - assert_eq!(0u64.to_signed(), 0i64); - assert_eq!(0u128.to_signed(), 0i128); - assert_eq!(0usize.to_signed(), 0isize); - } - - #[test] - fn test_to_signed_negative() { - assert_eq!((-1i8).to_signed(), -1i8); - assert_eq!((-1i16).to_signed(), -1i16); - assert_eq!((-1i32).to_signed(), -1i32); - assert_eq!((-1i64).to_signed(), -1i64); - assert_eq!((-1i128).to_signed(), -1i128); - assert_eq!((-1isize).to_signed(), -1isize); - } - - // ========================================== - // Integration tests - using traits together - // ========================================== - - #[test] - fn test_link_reference_can_be_converted_to_signed() { - fn use_link_reference(val: T) -> ::Type { - val.to_signed() - } - assert_eq!(use_link_reference(42u8), 42i8); - assert_eq!(use_link_reference(42u16), 42i16); - assert_eq!(use_link_reference(42u32), 42i32); - assert_eq!(use_link_reference(42u64), 42i64); - assert_eq!(use_link_reference(42usize), 42isize); - } - - #[test] - fn test_link_reference_has_max_value() { - fn get_max() -> T { - T::MAX - } - assert_eq!(get_max::(), u8::MAX); - assert_eq!(get_max::(), u16::MAX); - assert_eq!(get_max::(), u32::MAX); - assert_eq!(get_max::(), u64::MAX); - assert_eq!(get_max::(), usize::MAX); - } - - #[test] - fn test_number_default_values() { - fn check_default() -> T { - T::default() - } - assert_eq!(check_default::(), 0i8); - assert_eq!(check_default::(), 0u8); - assert_eq!(check_default::(), 0i16); - assert_eq!(check_default::(), 0u16); - assert_eq!(check_default::(), 0i32); - assert_eq!(check_default::(), 0u32); - assert_eq!(check_default::(), 0i64); - assert_eq!(check_default::(), 0u64); - assert_eq!(check_default::(), 0i128); - assert_eq!(check_default::(), 0u128); - assert_eq!(check_default::(), 0isize); - assert_eq!(check_default::(), 0usize); - } - - #[test] - fn test_number_as_usize() { - fn to_usize(val: T) -> usize { - val.as_() - } - assert_eq!(to_usize(42i8), 42usize); - assert_eq!(to_usize(42u8), 42usize); - assert_eq!(to_usize(42i16), 42usize); - assert_eq!(to_usize(42u16), 42usize); - assert_eq!(to_usize(42i32), 42usize); - assert_eq!(to_usize(42u32), 42usize); - } - - #[test] - fn test_signed_number_signum() { - fn get_signum(val: T) -> T { - val.signum() - } - assert_eq!(get_signum(5i8), 1i8); - assert_eq!(get_signum(-5i8), -1i8); - assert_eq!(get_signum(0i8), 0i8); - assert_eq!(get_signum(5i32), 1i32); - assert_eq!(get_signum(-5i32), -1i32); - assert_eq!(get_signum(0i32), 0i32); - } - - #[test] - fn test_signed_number_abs() { - fn get_abs(val: T) -> T { - val.abs() - } - assert_eq!(get_abs(-5i8), 5i8); - assert_eq!(get_abs(5i8), 5i8); - assert_eq!(get_abs(-100i32), 100i32); - assert_eq!(get_abs(100i32), 100i32); - } -} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 3437a90..d2f676a 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,3 +1,34 @@ +//! # platform_num +//! +//! Numeric traits for the [Links Platform](https://github.com/linksplatform). +//! +//! This crate provides a set of marker and conversion traits that abstract +//! over Rust's primitive integer types. They are used throughout the Links +//! Platform ecosystem to write generic code that works with any integer +//! type while preserving type safety. +//! +//! ## Traits +//! +//! | Trait | Description | +//! |-------|-------------| +//! | [`Number`] | Base numeric trait — any `PrimInt + Default + Debug + AsPrimitive + ToPrimitive` | +//! | [`SignedNumber`] | Extends [`Number`] with signed operations (`Signed + FromPrimitive`) | +//! | [`ToSigned`] | Converts an unsigned type to its signed counterpart (e.g. `u32` → `i32`) | +//! | [`MaxValue`] | Provides a `MAX` associated constant for every primitive integer type | +//! | [`LinkReference`] | Composite trait for link identifiers — unsigned, hashable, displayable, thread-safe | +//! +//! ## Example +//! +//! ``` +//! use platform_num::{LinkReference, MaxValue, Number, SignedNumber, ToSigned}; +//! +//! fn max_link() -> T { +//! T::MAX +//! } +//! +//! assert_eq!(max_link::(), u32::MAX); +//! ``` + mod imp; pub use imp::{LinkReference, MaxValue, Number, SignedNumber, ToSigned}; diff --git a/rust/tests/traits.rs b/rust/tests/traits.rs new file mode 100644 index 0000000..d2b5950 --- /dev/null +++ b/rust/tests/traits.rs @@ -0,0 +1,591 @@ +use platform_num::{LinkReference, MaxValue, Number, SignedNumber, ToSigned}; + +// ========================================== +// Tests for Number trait +// ========================================== + +#[test] +fn test_number_trait_for_i8() { + fn assert_number(_val: T) {} + assert_number(0i8); +} + +#[test] +fn test_number_trait_for_u8() { + fn assert_number(_val: T) {} + assert_number(0u8); +} + +#[test] +fn test_number_trait_for_i16() { + fn assert_number(_val: T) {} + assert_number(0i16); +} + +#[test] +fn test_number_trait_for_u16() { + fn assert_number(_val: T) {} + assert_number(0u16); +} + +#[test] +fn test_number_trait_for_i32() { + fn assert_number(_val: T) {} + assert_number(0i32); +} + +#[test] +fn test_number_trait_for_u32() { + fn assert_number(_val: T) {} + assert_number(0u32); +} + +#[test] +fn test_number_trait_for_i64() { + fn assert_number(_val: T) {} + assert_number(0i64); +} + +#[test] +fn test_number_trait_for_u64() { + fn assert_number(_val: T) {} + assert_number(0u64); +} + +#[test] +fn test_number_trait_for_i128() { + fn assert_number(_val: T) {} + assert_number(0i128); +} + +#[test] +fn test_number_trait_for_u128() { + fn assert_number(_val: T) {} + assert_number(0u128); +} + +#[test] +fn test_number_trait_for_isize() { + fn assert_number(_val: T) {} + assert_number(0isize); +} + +#[test] +fn test_number_trait_for_usize() { + fn assert_number(_val: T) {} + assert_number(0usize); +} + +// ========================================== +// Tests for SignedNumber trait +// ========================================== + +#[test] +fn test_signed_number_trait_for_i8() { + fn assert_signed_number(_val: T) {} + assert_signed_number(0i8); +} + +#[test] +fn test_signed_number_trait_for_i16() { + fn assert_signed_number(_val: T) {} + assert_signed_number(0i16); +} + +#[test] +fn test_signed_number_trait_for_i32() { + fn assert_signed_number(_val: T) {} + assert_signed_number(0i32); +} + +#[test] +fn test_signed_number_trait_for_i64() { + fn assert_signed_number(_val: T) {} + assert_signed_number(0i64); +} + +#[test] +fn test_signed_number_trait_for_i128() { + fn assert_signed_number(_val: T) {} + assert_signed_number(0i128); +} + +#[test] +fn test_signed_number_trait_for_isize() { + fn assert_signed_number(_val: T) {} + assert_signed_number(0isize); +} + +// ========================================== +// Tests for ToSigned trait +// ========================================== + +#[test] +fn test_to_signed_i8() { + let val: i8 = 42; + let signed: i8 = val.to_signed(); + assert_eq!(signed, 42i8); +} + +#[test] +fn test_to_signed_u8() { + let val: u8 = 42; + let signed: i8 = val.to_signed(); + assert_eq!(signed, 42i8); +} + +#[test] +fn test_to_signed_i16() { + let val: i16 = 1000; + let signed: i16 = val.to_signed(); + assert_eq!(signed, 1000i16); +} + +#[test] +fn test_to_signed_u16() { + let val: u16 = 1000; + let signed: i16 = val.to_signed(); + assert_eq!(signed, 1000i16); +} + +#[test] +fn test_to_signed_i32() { + let val: i32 = 100000; + let signed: i32 = val.to_signed(); + assert_eq!(signed, 100000i32); +} + +#[test] +fn test_to_signed_u32() { + let val: u32 = 100000; + let signed: i32 = val.to_signed(); + assert_eq!(signed, 100000i32); +} + +#[test] +fn test_to_signed_i64() { + let val: i64 = 1000000000; + let signed: i64 = val.to_signed(); + assert_eq!(signed, 1000000000i64); +} + +#[test] +fn test_to_signed_u64() { + let val: u64 = 1000000000; + let signed: i64 = val.to_signed(); + assert_eq!(signed, 1000000000i64); +} + +#[test] +fn test_to_signed_i128() { + let val: i128 = 1000000000000000; + let signed: i128 = val.to_signed(); + assert_eq!(signed, 1000000000000000i128); +} + +#[test] +fn test_to_signed_u128() { + let val: u128 = 1000000000000000; + let signed: i128 = val.to_signed(); + assert_eq!(signed, 1000000000000000i128); +} + +#[test] +fn test_to_signed_isize() { + let val: isize = 12345; + let signed: isize = val.to_signed(); + assert_eq!(signed, 12345isize); +} + +#[test] +fn test_to_signed_usize() { + let val: usize = 12345; + let signed: isize = val.to_signed(); + assert_eq!(signed, 12345isize); +} + +#[test] +fn test_to_signed_type_alias_i8() { + fn check>(_val: T) {} + check(0i8); + check(0u8); +} + +#[test] +fn test_to_signed_type_alias_i16() { + fn check>(_val: T) {} + check(0i16); + check(0u16); +} + +#[test] +fn test_to_signed_type_alias_i32() { + fn check>(_val: T) {} + check(0i32); + check(0u32); +} + +#[test] +fn test_to_signed_type_alias_i64() { + fn check>(_val: T) {} + check(0i64); + check(0u64); +} + +#[test] +fn test_to_signed_type_alias_i128() { + fn check>(_val: T) {} + check(0i128); + check(0u128); +} + +#[test] +fn test_to_signed_type_alias_isize() { + fn check>(_val: T) {} + check(0isize); + check(0usize); +} + +// ========================================== +// Tests for MaxValue trait +// ========================================== + +#[test] +fn test_max_value_i8() { + assert_eq!(i8::MAX, ::MAX); +} + +#[test] +fn test_max_value_u8() { + assert_eq!(u8::MAX, ::MAX); +} + +#[test] +fn test_max_value_i16() { + assert_eq!(i16::MAX, ::MAX); +} + +#[test] +fn test_max_value_u16() { + assert_eq!(u16::MAX, ::MAX); +} + +#[test] +fn test_max_value_i32() { + assert_eq!(i32::MAX, ::MAX); +} + +#[test] +fn test_max_value_u32() { + assert_eq!(u32::MAX, ::MAX); +} + +#[test] +fn test_max_value_i64() { + assert_eq!(i64::MAX, ::MAX); +} + +#[test] +fn test_max_value_u64() { + assert_eq!(u64::MAX, ::MAX); +} + +#[test] +fn test_max_value_i128() { + assert_eq!(i128::MAX, ::MAX); +} + +#[test] +fn test_max_value_u128() { + assert_eq!(u128::MAX, ::MAX); +} + +#[test] +fn test_max_value_isize() { + assert_eq!(isize::MAX, ::MAX); +} + +#[test] +fn test_max_value_usize() { + assert_eq!(usize::MAX, ::MAX); +} + +// ========================================== +// Tests for LinkReference trait +// ========================================== + +#[test] +fn test_link_reference_for_u8() { + fn assert_link_reference(_val: T) {} + assert_link_reference(0u8); +} + +#[test] +fn test_link_reference_for_u16() { + fn assert_link_reference(_val: T) {} + assert_link_reference(0u16); +} + +#[test] +fn test_link_reference_for_u32() { + fn assert_link_reference(_val: T) {} + assert_link_reference(0u32); +} + +#[test] +fn test_link_reference_for_u64() { + fn assert_link_reference(_val: T) {} + assert_link_reference(0u64); +} + +#[test] +fn test_link_reference_for_usize() { + fn assert_link_reference(_val: T) {} + assert_link_reference(0usize); +} + +// ========================================== +// Edge case tests for ToSigned +// ========================================== + +#[test] +fn test_to_signed_u8_max() { + let val: u8 = u8::MAX; + let signed: i8 = val.to_signed(); + assert_eq!(signed, -1i8); // Wrapping behavior +} + +#[test] +fn test_to_signed_u16_max() { + let val: u16 = u16::MAX; + let signed: i16 = val.to_signed(); + assert_eq!(signed, -1i16); // Wrapping behavior +} + +#[test] +fn test_to_signed_u32_max() { + let val: u32 = u32::MAX; + let signed: i32 = val.to_signed(); + assert_eq!(signed, -1i32); // Wrapping behavior +} + +#[test] +fn test_to_signed_u64_max() { + let val: u64 = u64::MAX; + let signed: i64 = val.to_signed(); + assert_eq!(signed, -1i64); // Wrapping behavior +} + +#[test] +fn test_to_signed_u128_max() { + let val: u128 = u128::MAX; + let signed: i128 = val.to_signed(); + assert_eq!(signed, -1i128); // Wrapping behavior +} + +#[test] +fn test_to_signed_usize_max() { + let val: usize = usize::MAX; + let signed: isize = val.to_signed(); + assert_eq!(signed, -1isize); // Wrapping behavior +} + +#[test] +fn test_to_signed_zero() { + assert_eq!(0u8.to_signed(), 0i8); + assert_eq!(0u16.to_signed(), 0i16); + assert_eq!(0u32.to_signed(), 0i32); + assert_eq!(0u64.to_signed(), 0i64); + assert_eq!(0u128.to_signed(), 0i128); + assert_eq!(0usize.to_signed(), 0isize); +} + +#[test] +fn test_to_signed_negative() { + assert_eq!((-1i8).to_signed(), -1i8); + assert_eq!((-1i16).to_signed(), -1i16); + assert_eq!((-1i32).to_signed(), -1i32); + assert_eq!((-1i64).to_signed(), -1i64); + assert_eq!((-1i128).to_signed(), -1i128); + assert_eq!((-1isize).to_signed(), -1isize); +} + +// ========================================== +// Integration tests - using traits together +// ========================================== + +#[test] +fn test_link_reference_can_be_converted_to_signed() { + fn use_link_reference(val: T) -> ::Type { + val.to_signed() + } + assert_eq!(use_link_reference(42u8), 42i8); + assert_eq!(use_link_reference(42u16), 42i16); + assert_eq!(use_link_reference(42u32), 42i32); + assert_eq!(use_link_reference(42u64), 42i64); + assert_eq!(use_link_reference(42usize), 42isize); +} + +#[test] +fn test_link_reference_has_max_value() { + fn get_max() -> T { + T::MAX + } + assert_eq!(get_max::(), u8::MAX); + assert_eq!(get_max::(), u16::MAX); + assert_eq!(get_max::(), u32::MAX); + assert_eq!(get_max::(), u64::MAX); + assert_eq!(get_max::(), usize::MAX); +} + +#[test] +fn test_number_default_values() { + fn check_default() -> T { + T::default() + } + assert_eq!(check_default::(), 0i8); + assert_eq!(check_default::(), 0u8); + assert_eq!(check_default::(), 0i16); + assert_eq!(check_default::(), 0u16); + assert_eq!(check_default::(), 0i32); + assert_eq!(check_default::(), 0u32); + assert_eq!(check_default::(), 0i64); + assert_eq!(check_default::(), 0u64); + assert_eq!(check_default::(), 0i128); + assert_eq!(check_default::(), 0u128); + assert_eq!(check_default::(), 0isize); + assert_eq!(check_default::(), 0usize); +} + +#[test] +fn test_number_as_usize() { + fn to_usize(val: T) -> usize { + val.as_() + } + assert_eq!(to_usize(42i8), 42usize); + assert_eq!(to_usize(42u8), 42usize); + assert_eq!(to_usize(42i16), 42usize); + assert_eq!(to_usize(42u16), 42usize); + assert_eq!(to_usize(42i32), 42usize); + assert_eq!(to_usize(42u32), 42usize); +} + +#[test] +fn test_signed_number_signum() { + fn get_signum(val: T) -> T { + val.signum() + } + assert_eq!(get_signum(5i8), 1i8); + assert_eq!(get_signum(-5i8), -1i8); + assert_eq!(get_signum(0i8), 0i8); + assert_eq!(get_signum(5i32), 1i32); + assert_eq!(get_signum(-5i32), -1i32); + assert_eq!(get_signum(0i32), 0i32); +} + +#[test] +fn test_signed_number_abs() { + fn get_abs(val: T) -> T { + val.abs() + } + assert_eq!(get_abs(-5i8), 5i8); + assert_eq!(get_abs(5i8), 5i8); + assert_eq!(get_abs(-100i32), 100i32); + assert_eq!(get_abs(100i32), 100i32); +} + +// ========================================== +// Tests for Number trait - ToPrimitive +// ========================================== + +#[test] +fn test_number_to_primitive() { + fn check_to_primitive(val: T) -> Option { + num_traits::ToPrimitive::to_f64(&val) + } + assert_eq!(check_to_primitive(42i32), Some(42.0)); + assert_eq!(check_to_primitive(42u64), Some(42.0)); + assert_eq!(check_to_primitive(0i8), Some(0.0)); +} + +// ========================================== +// Tests for Number trait - PrimInt operations +// ========================================== + +#[test] +fn test_number_prim_int_operations() { + fn check_ops(a: T, b: T) -> T { + a | b + } + assert_eq!(check_ops(0b1010u8, 0b0101u8), 0b1111u8); + assert_eq!(check_ops(0b1100u32, 0b0011u32), 0b1111u32); +} + +#[test] +fn test_number_count_ones() { + fn count(val: T) -> u32 { + num_traits::PrimInt::count_ones(val) + } + assert_eq!(count(0b1111u8), 4); + assert_eq!(count(0u32), 0); + assert_eq!(count(u64::MAX), 64); +} + +// ========================================== +// Tests for SignedNumber - FromPrimitive +// ========================================== + +#[test] +fn test_signed_number_from_primitive() { + fn from_i64(val: i64) -> Option { + num_traits::FromPrimitive::from_i64(val) + } + assert_eq!(from_i64::(42), Some(42i32)); + assert_eq!(from_i64::(127), Some(127i8)); + assert_eq!(from_i64::(128), None); // overflow +} + +// ========================================== +// Tests for LinkReference - Send + Sync +// ========================================== + +#[test] +fn test_link_reference_is_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); + assert_send_sync::(); + assert_send_sync::(); + assert_send_sync::(); + assert_send_sync::(); +} + +#[test] +fn test_link_reference_display() { + fn format_link(val: T) -> String { + format!("{val}") + } + assert_eq!(format_link(42u32), "42"); + assert_eq!(format_link(0u64), "0"); + assert_eq!(format_link(255u8), "255"); +} + +#[test] +fn test_link_reference_hash() { + use std::collections::HashSet; + + fn collect_links(vals: &[T]) -> HashSet { + vals.iter().copied().collect() + } + let set = collect_links(&[1u32, 2, 3, 1, 2]); + assert_eq!(set.len(), 3); +} + +#[test] +fn test_link_reference_from_primitive() { + fn link_from_u64(val: u64) -> Option { + num_traits::FromPrimitive::from_u64(val) + } + assert_eq!(link_from_u64::(42), Some(42u32)); + assert_eq!(link_from_u64::(255), Some(255u8)); + assert_eq!(link_from_u64::(256), None); // overflow +} From 1226b5558b01e5c8ef9e33b07c91abbb4de3dc8c Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 11 Apr 2026 05:42:22 +0000 Subject: [PATCH 5/6] refactor: integrate docs deployment into main rust.yml workflow Move rustdoc generation and GitHub Pages deployment from a separate deploy-rust-docs.yml workflow into the main rust.yml pipeline as a deploy-docs job that runs only after a successful release (auto-release or manual-release), matching the project's release-driven docs strategy. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/deploy-rust-docs.yml | 34 -------------------------- .github/workflows/rust.yml | 33 +++++++++++++++++++++++++ docs/case-studies/issue-142/README.md | 9 ++++--- 3 files changed, 38 insertions(+), 38 deletions(-) delete mode 100644 .github/workflows/deploy-rust-docs.yml diff --git a/.github/workflows/deploy-rust-docs.yml b/.github/workflows/deploy-rust-docs.yml deleted file mode 100644 index 96b4615..0000000 --- a/.github/workflows/deploy-rust-docs.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Deploy Rust Docs - -on: - push: - branches: main - paths: - - 'rust/**' - - '.github/workflows/deploy-rust-docs.yml' - workflow_dispatch: - -permissions: - contents: write - -jobs: - deploy-docs: - name: Build and Deploy Rust Documentation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - - - name: Build documentation - working-directory: rust - run: cargo doc --no-deps - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: rust/target/doc - destination_dir: rust - keep_files: true diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index df8905f..2c25037 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -477,3 +477,36 @@ jobs: 1. Review the changelog fragment in this PR 2. Merge this PR to main 3. The automated release workflow will publish to crates.io and create a GitHub release + + # === DEPLOY RUST DOCUMENTATION === + # Deploy generated Rust documentation to GitHub Pages after a successful release + deploy-docs: + name: Deploy Rust Documentation + needs: [auto-release, manual-release] + # Run if either release job succeeded (the other will be skipped) + if: | + always() && !cancelled() && ( + needs.auto-release.result == 'success' || + needs.manual-release.result == 'success' + ) + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: main + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build documentation + run: cargo doc --no-deps + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: rust/target/doc + destination_dir: rust + keep_files: true diff --git a/docs/case-studies/issue-142/README.md b/docs/case-studies/issue-142/README.md index 7e75777..5ec982d 100644 --- a/docs/case-studies/issue-142/README.md +++ b/docs/case-studies/issue-142/README.md @@ -78,13 +78,14 @@ The issue requested a comprehensive audit of the Rust codebase: **Finding:** The C# part of the project already has automated docs via `docfx`, deployed to GitHub Pages at `csharp/` subdirectory on the `gh-pages` branch. -**Action taken:** Added `.github/workflows/deploy-rust-docs.yml` workflow that: -- Triggers on push to `main` when Rust files change +**Action taken:** Added a `deploy-docs` job to the main `.github/workflows/rust.yml` workflow that: +- Runs only after a successful release (auto-release or manual-release) +- Checks out the `main` branch to get the released code - Runs `cargo doc --no-deps` to generate HTML documentation - Deploys to `rust/` subdirectory on the `gh-pages` branch - Uses `peaceiris/actions-gh-pages@v4` (same approach as the C# docs) -This makes Rust documentation available alongside C# documentation on the project's GitHub Pages site. +This ensures Rust documentation is regenerated and published only when a new version is released, matching the project's release-driven documentation strategy. --- @@ -102,7 +103,7 @@ This makes Rust documentation available alongside C# documentation on the projec 5. **Test restructuring:** Moved tests from `src/` to `tests/` directory and added new test coverage. -6. **Automated docs deployment:** Added CI workflow for Rust documentation generation and GitHub Pages deployment. +6. **Automated docs deployment:** Integrated Rust documentation generation and GitHub Pages deployment into the main CI/CD workflow, triggered after successful releases. --- From 264323dcbfa40dd9fbf986c3dcb71485e2171c29 Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 11 Apr 2026 05:44:04 +0000 Subject: [PATCH 6/6] docs: update changelog fragment to reflect actual changes Accurately describe TODO resolution (implemented, not removed), add entries for new tests, docs deployment, and test restructuring. Co-Authored-By: Claude Opus 4.6 --- changelog.d/20260411_000000_edition_2024_audit.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/changelog.d/20260411_000000_edition_2024_audit.md b/changelog.d/20260411_000000_edition_2024_audit.md index 5ba8ee9..bbf6f99 100644 --- a/changelog.d/20260411_000000_edition_2024_audit.md +++ b/changelog.d/20260411_000000_edition_2024_audit.md @@ -5,12 +5,17 @@ bump: minor ### Changed - Upgrade Rust edition from 2021 to 2024 (stable since Rust 1.85) - Bump MSRV from 1.70 to 1.85 (required for edition 2024) +- Move tests from `src/imp.rs` to `tests/traits.rs` as proper integration tests ### Fixed - Fix CONTRIBUTING.md incorrectly stating "nightly toolchain" (project uses stable) - Fix README.md showing outdated install version `0.4` instead of `0.5` -- Remove stale TODO comments from source code - Regenerate Cargo.lock to match current version (0.5.0) ### Added +- Implement `for_each_integer_type!` macro (resolves TODO) +- Document IDE alias design decision for `LinkReference` trait (resolves TODO) +- Add 8 new integration tests (total: 75 integration + 6 doc tests) +- Add crate-level documentation in `lib.rs` +- Add automated rustdoc deployment to GitHub Pages after release - Add case study documentation for issue #142 audit findings