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/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..bbf6f99 --- /dev/null +++ b/changelog.d/20260411_000000_edition_2024_audit.md @@ -0,0 +1,21 @@ +--- +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` +- 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 diff --git a/docs/case-studies/issue-142/README.md b/docs/case-studies/issue-142/README.md new file mode 100644 index 0000000..5ec982d --- /dev/null +++ b/docs/case-studies/issue-142/README.md @@ -0,0 +1,154 @@ +# 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:** 75 integration tests + 6 doc tests, all passing. +- **Architecture:** Clean trait design with blanket implementations and zero-cost abstractions. + +**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 + +**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/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 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 ensures Rust documentation is regenerated and published only when a new version is released, matching the project's release-driven documentation strategy. + +--- + +## Solution Plan + +### Approach: Targeted Improvements + +1. **Edition upgrade (2021 → 2024):** Adopts the latest stable edition, bringing the crate in line with modern Rust ecosystem standards. Zero code changes required. + +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. + +3. **Documentation improvements:** Fixed factual inaccuracies, added crate-level docs, enhanced trait documentation with design notes. + +4. **TODO resolution:** Instead of removing TODOs, implemented the requested `for_each_integer_type!` macro and documented the IDE alias design decision. + +5. **Test restructuring:** Moved tests from `src/` to `tests/` directory and added new test coverage. + +6. **Automated docs deployment:** Integrated Rust documentation generation and GitHub Pages deployment into the main CI/CD workflow, triggered after successful releases. + +--- + +## 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. + +### 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 + +All quality checks pass after changes: + +- `cargo fmt --check` — ✅ clean +- `cargo clippy --all-targets --all-features` — ✅ zero warnings +- `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/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..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,19 +136,7 @@ macro_rules! max_value_impl { }; } -// TODO: Create macro foreach -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. /// @@ -136,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 /// /// ``` @@ -148,7 +164,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 @@ -177,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 +}