From 7e7c7bbbbb4df7c0fc19d21cb9289d8e1ab9f771 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 4 Oct 2026 16:39:55 +0200 Subject: [PATCH 1/3] Consolidate recursive tuple utilities and release verification --- .cargo/mutants-reviewed-equivalents.json | 39 ++ .cargo/mutants.toml | 2 + .github/copilot-instructions.md | 46 +- .github/dependabot.yml | 4 + .github/workflows/downstream.yml | 86 +++ .github/workflows/rust.yml | 268 +++++++- .github/workflows/security.yml | 7 +- .github/workflows/verification.yml | 49 ++ .github/workflows/workflow-validation.yml | 42 ++ .gitignore | 4 +- CHANGELOG.md | 21 +- Cargo.lock | 292 +-------- Cargo.toml | 79 +-- README.md | 298 +-------- measure_compile_times.sh | 48 -- tools/check_mutants.py | 304 +++++++++ tools/tests/test_check_mutants.py | 347 ++++++++++ tools/tests/test_diesel_builders_revisions.py | 61 ++ tools/tests/test_harness_syntax.py | 45 ++ tools/tests/test_package_failures.py | 40 ++ tools/verify_compile_workloads.sh | 349 ++++++++++ tools/verify_diesel_builders.sh | 604 ++++++++++++++++++ tools/verify_harness_syntax.py | 48 ++ tools/verify_package.sh | 448 +++++++++++++ tuplities-clone/Cargo.toml | 28 - tuplities-clone/src/lib.rs | 24 - tuplities-copy/Cargo.toml | 29 - tuplities-copy/src/lib.rs | 26 - tuplities-debug/Cargo.toml | 28 - tuplities-debug/src/lib.rs | 24 - tuplities-default/Cargo.toml | 28 - tuplities-default/src/lib.rs | 22 - tuplities-derive/Cargo.toml | 35 - tuplities-derive/src/lib.rs | 303 --------- tuplities-derive/src/tuple_size.rs | 118 ---- tuplities-derive/src/tuplities_as_ref.rs | 25 - tuplities-derive/src/tuplities_clone.rs | 24 - tuplities-derive/src/tuplities_copy.rs | 23 - tuplities-derive/src/tuplities_debug.rs | 31 - tuplities-derive/src/tuplities_default.rs | 22 - tuplities-derive/src/tuplities_eq.rs | 29 - .../src/tuplities_first_tuple_row.rs | 24 - tuplities-derive/src/tuplities_from.rs | 33 - tuplities-derive/src/tuplities_hash.rs | 25 - tuplities-derive/src/tuplities_index.rs | 39 -- tuplities-derive/src/tuplities_insert.rs | 35 - .../src/tuplities_last_tuple_row.rs | 24 - tuplities-derive/src/tuplities_len.rs | 19 - tuplities-derive/src/tuplities_mut.rs | 32 - tuplities-derive/src/tuplities_mut_map.rs | 25 - tuplities-derive/src/tuplities_nest_tuple.rs | 157 ----- tuplities-derive/src/tuplities_option.rs | 45 -- tuplities-derive/src/tuplities_ord.rs | 29 - tuplities-derive/src/tuplities_partial_eq.rs | 29 - tuplities-derive/src/tuplities_partial_ord.rs | 29 - tuplities-derive/src/tuplities_pop_back.rs | 44 -- tuplities-derive/src/tuplities_pop_front.rs | 44 -- tuplities-derive/src/tuplities_push_back.rs | 24 - tuplities-derive/src/tuplities_push_front.rs | 25 - tuplities-derive/src/tuplities_ref.rs | 25 - tuplities-derive/src/tuplities_ref_map.rs | 25 - tuplities-derive/src/tuplities_remove.rs | 47 -- tuplities-derive/src/tuplities_replicate.rs | 59 -- tuplities-derive/src/tuplities_reverse.rs | 27 - tuplities-derive/src/tuplities_row.rs | 24 - tuplities-derive/src/tuplities_row_mut.rs | 22 - tuplities-derive/src/tuplities_split.rs | 36 -- tuplities-derive/src/tuplities_try_from.rs | 38 -- tuplities-eq/Cargo.toml | 28 - tuplities-eq/src/lib.rs | 27 - tuplities-flatten-nest/Cargo.toml | 30 - tuplities-flatten-nest/src/lib.rs | 46 -- tuplities-from/Cargo.toml | 24 - tuplities-from/src/lib.rs | 56 -- tuplities-hash/Cargo.toml | 29 - tuplities-hash/src/lib.rs | 14 - tuplities-index/Cargo.toml | 30 - tuplities-index/src/lib.rs | 119 ---- tuplities-insert/Cargo.toml | 29 - tuplities-insert/src/lib.rs | 85 --- tuplities-len/Cargo.toml | 29 - tuplities-len/src/lib.rs | 89 --- tuplities-mut/Cargo.toml | 28 - tuplities-mut/src/lib.rs | 72 --- tuplities-option/Cargo.toml | 28 - tuplities-option/src/lib.rs | 54 -- tuplities-ord/Cargo.toml | 54 -- tuplities-ord/src/lib.rs | 30 - tuplities-partial-eq/Cargo.toml | 28 - tuplities-partial-eq/src/lib.rs | 25 - tuplities-partial-ord/Cargo.toml | 29 - tuplities-partial-ord/src/lib.rs | 29 - tuplities-pop-back/Cargo.toml | 28 - tuplities-pop-back/src/lib.rs | 95 --- tuplities-pop-front/Cargo.toml | 28 - tuplities-pop-front/src/lib.rs | 96 --- tuplities-push-back/Cargo.toml | 28 - tuplities-push-back/src/lib.rs | 39 -- tuplities-push-front/Cargo.toml | 27 - tuplities-push-front/src/lib.rs | 39 -- tuplities-ref/Cargo.toml | 28 - tuplities-ref/src/lib.rs | 68 -- tuplities-remove/Cargo.toml | 29 - tuplities-remove/src/lib.rs | 73 --- tuplities-replicate/Cargo.toml | 28 - tuplities-replicate/src/lib.rs | 41 -- tuplities-reverse/Cargo.toml | 28 - tuplities-reverse/src/lib.rs | 59 -- tuplities-row/Cargo.toml | 70 -- tuplities-row/src/lib.rs | 113 ---- tuplities-split/Cargo.toml | 29 - tuplities-split/src/lib.rs | 111 ---- tuplities-try-from/Cargo.toml | 27 - tuplities-try-from/src/lib.rs | 74 --- tuplities/Cargo.toml | 282 +------- tuplities/LICENSE | 1 + tuplities/src/flat_bridge.rs | 134 ++++ .../src/flatten_nested.rs | 32 +- tuplities/src/lib.rs | 138 ++-- .../src/matrix.rs | 73 ++- .../src/nest.rs | 151 ++++- .../src/nested_chain.rs | 36 +- .../src/nested_index.rs | 109 ++-- .../src/nested_into_vec.rs | 16 +- .../src/nested_option.rs | 134 ++-- .../src/nested_option_try_from.rs | 83 ++- .../src/nested_push_pop.rs | 106 +++ .../src/nested_ref.rs | 20 + .../src/nested_replicate.rs | 9 +- tuplities/src/nested_reverse.rs | 134 ++++ .../src/nested_row.rs | 94 ++- .../src/nested_starts_with.rs | 0 tuplities/src/nested_structure.rs | 247 +++++++ .../src/nested_try_from.rs | 56 +- tuplities/src/nested_tuple.rs | 88 +++ tuplities/tests/construction_macros.rs | 53 ++ tuplities/tests/flat_bridge_contracts.rs | 44 ++ tuplities/tests/flat_bridge_sizes.rs | 66 ++ tuplities/tests/recursive_index.rs | 122 ++++ tuplities/tests/recursive_structure.rs | 216 +++++++ 140 files changed, 4713 insertions(+), 5236 deletions(-) create mode 100644 .cargo/mutants-reviewed-equivalents.json create mode 100644 .cargo/mutants.toml create mode 100644 .github/workflows/downstream.yml create mode 100644 .github/workflows/verification.yml create mode 100644 .github/workflows/workflow-validation.yml delete mode 100755 measure_compile_times.sh create mode 100644 tools/check_mutants.py create mode 100644 tools/tests/test_check_mutants.py create mode 100644 tools/tests/test_diesel_builders_revisions.py create mode 100644 tools/tests/test_harness_syntax.py create mode 100644 tools/tests/test_package_failures.py create mode 100755 tools/verify_compile_workloads.sh create mode 100755 tools/verify_diesel_builders.sh create mode 100755 tools/verify_harness_syntax.py create mode 100755 tools/verify_package.sh delete mode 100644 tuplities-clone/Cargo.toml delete mode 100644 tuplities-clone/src/lib.rs delete mode 100644 tuplities-copy/Cargo.toml delete mode 100644 tuplities-copy/src/lib.rs delete mode 100644 tuplities-debug/Cargo.toml delete mode 100644 tuplities-debug/src/lib.rs delete mode 100644 tuplities-default/Cargo.toml delete mode 100644 tuplities-default/src/lib.rs delete mode 100644 tuplities-derive/Cargo.toml delete mode 100644 tuplities-derive/src/lib.rs delete mode 100644 tuplities-derive/src/tuple_size.rs delete mode 100644 tuplities-derive/src/tuplities_as_ref.rs delete mode 100644 tuplities-derive/src/tuplities_clone.rs delete mode 100644 tuplities-derive/src/tuplities_copy.rs delete mode 100644 tuplities-derive/src/tuplities_debug.rs delete mode 100644 tuplities-derive/src/tuplities_default.rs delete mode 100644 tuplities-derive/src/tuplities_eq.rs delete mode 100644 tuplities-derive/src/tuplities_first_tuple_row.rs delete mode 100644 tuplities-derive/src/tuplities_from.rs delete mode 100644 tuplities-derive/src/tuplities_hash.rs delete mode 100644 tuplities-derive/src/tuplities_index.rs delete mode 100644 tuplities-derive/src/tuplities_insert.rs delete mode 100644 tuplities-derive/src/tuplities_last_tuple_row.rs delete mode 100644 tuplities-derive/src/tuplities_len.rs delete mode 100644 tuplities-derive/src/tuplities_mut.rs delete mode 100644 tuplities-derive/src/tuplities_mut_map.rs delete mode 100644 tuplities-derive/src/tuplities_nest_tuple.rs delete mode 100644 tuplities-derive/src/tuplities_option.rs delete mode 100644 tuplities-derive/src/tuplities_ord.rs delete mode 100644 tuplities-derive/src/tuplities_partial_eq.rs delete mode 100644 tuplities-derive/src/tuplities_partial_ord.rs delete mode 100644 tuplities-derive/src/tuplities_pop_back.rs delete mode 100644 tuplities-derive/src/tuplities_pop_front.rs delete mode 100644 tuplities-derive/src/tuplities_push_back.rs delete mode 100644 tuplities-derive/src/tuplities_push_front.rs delete mode 100644 tuplities-derive/src/tuplities_ref.rs delete mode 100644 tuplities-derive/src/tuplities_ref_map.rs delete mode 100644 tuplities-derive/src/tuplities_remove.rs delete mode 100644 tuplities-derive/src/tuplities_replicate.rs delete mode 100644 tuplities-derive/src/tuplities_reverse.rs delete mode 100644 tuplities-derive/src/tuplities_row.rs delete mode 100644 tuplities-derive/src/tuplities_row_mut.rs delete mode 100644 tuplities-derive/src/tuplities_split.rs delete mode 100644 tuplities-derive/src/tuplities_try_from.rs delete mode 100644 tuplities-eq/Cargo.toml delete mode 100644 tuplities-eq/src/lib.rs delete mode 100644 tuplities-flatten-nest/Cargo.toml delete mode 100644 tuplities-flatten-nest/src/lib.rs delete mode 100644 tuplities-from/Cargo.toml delete mode 100644 tuplities-from/src/lib.rs delete mode 100644 tuplities-hash/Cargo.toml delete mode 100644 tuplities-hash/src/lib.rs delete mode 100644 tuplities-index/Cargo.toml delete mode 100644 tuplities-index/src/lib.rs delete mode 100644 tuplities-insert/Cargo.toml delete mode 100644 tuplities-insert/src/lib.rs delete mode 100644 tuplities-len/Cargo.toml delete mode 100644 tuplities-len/src/lib.rs delete mode 100644 tuplities-mut/Cargo.toml delete mode 100644 tuplities-mut/src/lib.rs delete mode 100644 tuplities-option/Cargo.toml delete mode 100644 tuplities-option/src/lib.rs delete mode 100644 tuplities-ord/Cargo.toml delete mode 100644 tuplities-ord/src/lib.rs delete mode 100644 tuplities-partial-eq/Cargo.toml delete mode 100644 tuplities-partial-eq/src/lib.rs delete mode 100644 tuplities-partial-ord/Cargo.toml delete mode 100644 tuplities-partial-ord/src/lib.rs delete mode 100644 tuplities-pop-back/Cargo.toml delete mode 100644 tuplities-pop-back/src/lib.rs delete mode 100644 tuplities-pop-front/Cargo.toml delete mode 100644 tuplities-pop-front/src/lib.rs delete mode 100644 tuplities-push-back/Cargo.toml delete mode 100644 tuplities-push-back/src/lib.rs delete mode 100644 tuplities-push-front/Cargo.toml delete mode 100644 tuplities-push-front/src/lib.rs delete mode 100644 tuplities-ref/Cargo.toml delete mode 100644 tuplities-ref/src/lib.rs delete mode 100644 tuplities-remove/Cargo.toml delete mode 100644 tuplities-remove/src/lib.rs delete mode 100644 tuplities-replicate/Cargo.toml delete mode 100644 tuplities-replicate/src/lib.rs delete mode 100644 tuplities-reverse/Cargo.toml delete mode 100644 tuplities-reverse/src/lib.rs delete mode 100644 tuplities-row/Cargo.toml delete mode 100644 tuplities-row/src/lib.rs delete mode 100644 tuplities-split/Cargo.toml delete mode 100644 tuplities-split/src/lib.rs delete mode 100644 tuplities-try-from/Cargo.toml delete mode 100644 tuplities-try-from/src/lib.rs create mode 120000 tuplities/LICENSE create mode 100644 tuplities/src/flat_bridge.rs rename {tuplities-flatten-nest => tuplities}/src/flatten_nested.rs (70%) rename {tuplities-flatten-nest => tuplities}/src/matrix.rs (88%) rename {tuplities-flatten-nest => tuplities}/src/nest.rs (60%) rename {tuplities-flatten-nest => tuplities}/src/nested_chain.rs (81%) rename {tuplities-flatten-nest => tuplities}/src/nested_index.rs (62%) rename {tuplities-flatten-nest => tuplities}/src/nested_into_vec.rs (85%) rename {tuplities-flatten-nest => tuplities}/src/nested_option.rs (78%) rename {tuplities-flatten-nest => tuplities}/src/nested_option_try_from.rs (78%) rename {tuplities-flatten-nest => tuplities}/src/nested_push_pop.rs (71%) rename {tuplities-flatten-nest => tuplities}/src/nested_ref.rs (87%) rename {tuplities-flatten-nest => tuplities}/src/nested_replicate.rs (82%) create mode 100644 tuplities/src/nested_reverse.rs rename {tuplities-flatten-nest => tuplities}/src/nested_row.rs (69%) rename {tuplities-flatten-nest => tuplities}/src/nested_starts_with.rs (100%) create mode 100644 tuplities/src/nested_structure.rs rename {tuplities-flatten-nest => tuplities}/src/nested_try_from.rs (81%) create mode 100644 tuplities/src/nested_tuple.rs create mode 100644 tuplities/tests/construction_macros.rs create mode 100644 tuplities/tests/flat_bridge_contracts.rs create mode 100644 tuplities/tests/flat_bridge_sizes.rs create mode 100644 tuplities/tests/recursive_index.rs create mode 100644 tuplities/tests/recursive_structure.rs diff --git a/.cargo/mutants-reviewed-equivalents.json b/.cargo/mutants-reviewed-equivalents.json new file mode 100644 index 0000000..9f159bc --- /dev/null +++ b/.cargo/mutants-reviewed-equivalents.json @@ -0,0 +1,39 @@ +{ + "records": [ + { + "file": "tuplities/src/nested_into_vec.rs", + "function": " for ()>::into_vec -> Vec", + "replacement": "vec![]", + "sha256": "da55257b29ace1b1e12422a5eb5a8a277a1759ea74201479e9e495c11b41acbd", + "reason": "Both constructors produce an empty `Vec` without allocating." + }, + { + "file": "tuplities/src/nested_option.rs", + "function": "::transpose -> Option", + "replacement": "Some(Default::default())", + "sha256": "7d55fedd4dcce4cb0a0265fa856b97f7d96a1e5cf083f3a482c46b98c6b4bef0", + "reason": "`Self::Transposed` is `()`, whose default is `()`." + }, + { + "file": "tuplities/src/nested_option.rs", + "function": " for ()>::transpose_or -> Result", + "replacement": "Ok(Default::default())", + "sha256": "7d55fedd4dcce4cb0a0265fa856b97f7d96a1e5cf083f3a482c46b98c6b4bef0", + "reason": "`Self::Transposed` is `()`, whose default is `()`." + }, + { + "file": "tuplities/src/nested_option_try_from.rs", + "function": " for ()>::nested_tuple_option_try_from -> Result", + "replacement": "Ok(Default::default())", + "sha256": "ef7a1d449e1e2a22109936cfe16e6db8fa0cde4dc39abd673d80e5a68dcf6bcf", + "reason": "`Self` is `()`, whose default is `()`." + }, + { + "file": "tuplities/src/nested_try_from.rs", + "function": " for ()>::nested_tuple_try_from -> Result", + "replacement": "Ok(Default::default())", + "sha256": "cbfe4acde1b8d7143001355215a8e30baca156a7a9c85ec1db0abecb965509dd", + "reason": "`Self` is `()`, whose default is `()`." + } + ] +} diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml new file mode 100644 index 0000000..d59b17f --- /dev/null +++ b/.cargo/mutants.toml @@ -0,0 +1,2 @@ +profile = "mutants" +gitignore = true diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 0d9ef83..e71b004 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,42 +1,22 @@ # tuplities Copilot Instructions -## Architecture Overview -This is a Rust workspace providing tuple utilities. The project is split into three crates for improved compile times: +## Architecture -- `tuplities/`: Main crate that re-exports traits from subcrates -- `tuplities-{snake_trait_name}/`: One of several crates of the suite which provides the `Tuple{camel_trait_name}` trait with derive macro implementation -- `tuplities-derive/`: Procedural macro crate that generates all of the trait implementations for tuples of various sizes +The workspace contains one crate, `tuplities/`. Recursive utilities operate on `()`, singleton tuples `(T,)`, and nested pairs `(Head, Tail)`. -The derive macro generates implementations for tuples from size 0 (unit `()`) up to `MAX_TUPLE_SIZE`, controlled by Cargo features to balance functionality against compile time. +`tuplities/src/flat_bridge.rs` generates flat/nested boundary conversions through internal `macro_rules!` macros. The default flat limit is 8. Features `size-16`, `size-32`, `size-48`, `size-64`, `size-96`, and `size-128` select larger limits, with the largest enabled size taking precedence. -## Key Patterns - -- **Feature-gated sizes**: Use features like `size-16`, `size-32` to control maximum supported tuple size (default: 8). See `tuplities-derive/src/tuple_size.rs` for size configuration logic. -- **Procedural macro generation**: Implementations are generated using `generate_all_sizes()` function that iterates over tuple sizes. See `tuplities-derive/src/tuplities_clone.rs` for the pattern. -- **Type parameter generation**: Use `type_params()` and `indices()` helpers in `tuple_size.rs` for generating generic type lists and field access indices. - -## Developer Workflows -- Build all crates: `cargo build --workspace` -- Run tests: `cargo test --workspace` -- Enable larger tuple support: `cargo build --workspace --features size-32` (propagates to all crates) -- Debug macro expansion: `cargo expand --package tuplities-derive` - -## Code Examples -```rust -// Basic usage -use tuplities::prelude::TupleClone; - -let original = (1, "hello".to_string(), vec![1, 2, 3]); -let cloned = original.tuple_clone(); -assert_eq!(original, cloned); -``` +The crate is `no_std`. The `alloc` feature enables homogeneous vector conversion. The default `flatten-nest` feature enables flat tuple and matrix conversions and includes `alloc`. ## Conventions -- Strict linting: Workspace enforces `missing_docs = "forbid"` and extensive Clippy rules. Always document public APIs. -- Edition 2024: Use modern Rust features available in the 2024 edition. -- Workspace dependencies: Define shared deps in root `Cargo.toml` under `[workspace.dependencies]`. +- Use recursive trait implementations for nested tuple operations. +- Preserve public associated-type equalities used by downstream generic code. +- Keep shared dependencies under `[workspace.dependencies]`. +- Document public APIs and preserve the workspace's Rust and Clippy lints. +- The crate README provides crate-level documentation and executable doctests. +- Use Rust edition `2024` and support the declared minimum compiler. + +## Verification -## Integration Points -- External deps: `syn`, `quote`, `proc-macro2` for macro generation -- Cross-crate communication: Derive crate generates code used by clone crate, re-exported by main crate \ No newline at end of file +Scope checks to `-p tuplities`. Verify default features, `--no-default-features`, `alloc`, and supported flat width configurations. Exercise flat/nested round trips, mutable borrowing, and downstream behavior when changing the bridges. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 26e4d54..e58e908 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,7 @@ updates: # The reference selects the Rust toolchain rather than the action version, # so Dependabot reads 1.85.0 as a release and offers a nonexistent 1.120.0. - dependency-name: dtolnay/rust-toolchain + - package-ecosystem: cargo + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml new file mode 100644 index 0000000..2b217b2 --- /dev/null +++ b/.github/workflows/downstream.yml @@ -0,0 +1,86 @@ +name: Downstream verification + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + - cron: '0 4 * * 1' + workflow_dispatch: + +permissions: + contents: read + +jobs: + pinned-revisions: + name: Pinned revisions + if: github.event_name == 'push' || github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 90 + env: + CARGO_BUILD_JOBS: "4" + DIESEL_BUILDERS_REV: 7041ce661200fcf644d44c63e15695d880737945 + DIESEL_REV: 81f1cdb2927f6d0ca11039c16bca2ebd2c117be2 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable + - name: Install SQLite prerequisites + run: | + sudo apt-get update + sudo apt-get install --yes libsqlite3-dev pkg-config + - name: Cache cargo dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/.cargo/git + ~/.cargo/registry + key: ${{ runner.os }}-downstream-cargo-pinned-${{ hashFiles('tools/verify_diesel_builders.sh') }} + - name: Verify the pinned diesel-builders and diesel revisions + run: bash tools/verify_diesel_builders.sh + - name: Upload diagnostics + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: downstream-diagnostics-pinned + path: target/verification/diesel-builders/ + + moving-main: + name: Moving git main + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 90 + env: + CARGO_BUILD_JOBS: "4" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable + - name: Install SQLite prerequisites + run: | + sudo apt-get update + sudo apt-get install --yes libsqlite3-dev pkg-config + - name: Cache cargo dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/.cargo/git + ~/.cargo/registry + key: ${{ runner.os }}-downstream-cargo-moving-main + - name: Verify diesel-builders git main + run: bash tools/verify_diesel_builders.sh + - name: Upload diagnostics + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: downstream-diagnostics-moving-main + path: target/verification/diesel-builders/ diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6dd42f7..b4c71de 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,9 +5,13 @@ on: branches: [ main ] pull_request: branches: [ main ] + schedule: + - cron: '0 3 * * 1' + workflow_dispatch: env: CARGO_TERM_COLOR: always + CARGO_BUILD_JOBS: "4" permissions: contents: read @@ -15,15 +19,19 @@ permissions: jobs: test: name: Test + if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: ${{ matrix.os }} + timeout-minutes: 45 strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] rust: [stable, beta, nightly] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 with: toolchain: ${{ matrix.rust }} - name: Cache cargo registry @@ -40,63 +48,295 @@ jobs: uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-cargo-build-${{ matrix.rust }}-default-${{ hashFiles('**/Cargo.lock') }} - name: Run tests - run: cargo test --verbose + run: cargo test -p tuplities --verbose - name: Run tests (release) - run: cargo test --release --verbose + run: cargo test -p tuplities --release --verbose fmt: name: Rustfmt + if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 with: + toolchain: stable components: rustfmt - name: Check formatting - run: cargo fmt --all -- --check + run: cargo fmt -p tuplities -- --check clippy: name: Clippy + if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 with: + toolchain: stable components: clippy - name: Run clippy - run: cargo clippy --all-targets -- -D warnings + run: cargo clippy -p tuplities --all-targets --all-features -- -D warnings doc: name: Documentation + if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable - name: Check documentation - run: cargo doc --no-deps --document-private-items + run: cargo doc -p tuplities --no-deps --all-features --document-private-items env: RUSTDOCFLAGS: -D warnings + msrv: + name: MSRV 1.85.0 + if: github.event_name == 'push' || github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: 1.85.0 + - name: Cache cargo registry + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: target + key: ${{ runner.os }}-cargo-build-msrv-1.85.0-all-and-core-${{ hashFiles('**/Cargo.lock') }} + - name: Run tests + run: cargo test -p tuplities --all-features + - name: Run tests (no default features) + run: cargo test -p tuplities --no-default-features + + embedded: + name: no_std thumbv7em-none-eabi + if: github.event_name == 'push' || github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: 1.85.0 + targets: thumbv7em-none-eabi + - name: Cache cargo registry + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: target + key: ${{ runner.os }}-cargo-build-embedded-1.85.0-thumbv7em-none-eabi-core-${{ hashFiles('**/Cargo.lock') }} + - name: Check embedded target + run: cargo check -p tuplities --target thumbv7em-none-eabi --no-default-features + + feature-configuration: + name: Feature configuration + if: github.event_name == 'push' || github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + features: ${{ steps.discover.outputs.features }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Discover supported feature configurations + id: discover + run: | + python3 - <<'PY' + import json + import os + import tomllib + + with open("tuplities/Cargo.toml", "rb") as manifest: + features = tomllib.load(manifest)["features"] + + sizes = sorted( + (name for name in features if name.startswith("size-") and name[5:].isdigit()), + key=lambda name: int(name[5:]), + ) + configurations = [ + "--no-default-features --features alloc", + "--no-default-features --features flatten-nest", + ] + configurations += [ + f"--no-default-features --features flatten-nest,{size}" for size in sizes + ] + if len(sizes) >= 2: + configurations.append( + f"--no-default-features --features flatten-nest,{sizes[0]},{sizes[1]}" + ) + + with open(os.environ["GITHUB_OUTPUT"], "a") as output: + output.write(f"features={json.dumps(configurations)}") + PY + + features: + name: Features ${{ matrix.features }} + if: github.event_name == 'push' || github.event_name == 'pull_request' + needs: feature-configuration + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + matrix: + features: ${{ fromJSON(needs.feature-configuration.outputs.features) }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable + - name: Cache cargo registry + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: target + key: ${{ runner.os }}-cargo-build-features-stable-${{ matrix.features }}-${{ hashFiles('**/Cargo.lock') }} + - name: Run tests + env: + FEATURE_ARGS: ${{ matrix.features }} + run: | + read -r -a features <<< "$FEATURE_ARGS" + nice -n 10 cargo test -p tuplities "${features[@]}" + coverage: name: Code Coverage + if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: ubuntu-latest + timeout-minutes: 45 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust - uses: dtolnay/rust-toolchain@nightly + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 with: + toolchain: nightly components: llvm-tools-preview - - name: Install tarpaulin - uses: taiki-e/install-action@cargo-tarpaulin + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@e407f7bafb71fd004bc5c2da3032e5470cbb6ef0 + with: + tool: cargo-llvm-cov + fallback: none + - name: Cache cargo registry + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: target + key: ${{ runner.os }}-cargo-build-coverage-nightly-all-${{ hashFiles('**/Cargo.lock') }} - name: Generate coverage - run: cargo tarpaulin --engine=llvm --out xml --workspace --exclude tuplities-derive --run-types AllTargets --run-types Doctests + run: | + cargo llvm-cov -p tuplities --all-targets --all-features --no-report + cargo llvm-cov -p tuplities --doc --all-features --no-clean --lcov --output-path target/coverage.lcov - name: Upload coverage to Codecov uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: token: ${{ secrets.CODECOV_TOKEN }} + files: target/coverage.lcov fail_ci_if_error: false + + mutants: + name: Mutation testing + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 180 + env: + CARGO_BUILD_JOBS: "2" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable + - name: Cache cargo registry + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + - name: Install cargo-mutants + uses: taiki-e/install-action@e407f7bafb71fd004bc5c2da3032e5470cbb6ef0 + with: + tool: cargo-mutants@27.1.0 + fallback: none + - name: Run bounded mutation testing + run: | + status=0 + nice -n 10 cargo mutants -p tuplities --jobs 2 --jobserver-tasks 2 --all-features \ + --output "$RUNNER_TEMP/mutation-results" || status=$? + python3 -B tools/check_mutants.py \ + "$RUNNER_TEMP/mutation-results/mutants.out/outcomes.json" --exit-code "$status" + - name: Upload mutation reports + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: mutation-reports + path: ${{ runner.temp }}/mutation-results/mutants.out/ diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index bef6c39..d003d71 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -15,10 +15,15 @@ jobs: audit: name: Security Audit runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable - name: Install cargo-audit run: cargo install cargo-audit - name: Run security audit diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml new file mode 100644 index 0000000..85b2f58 --- /dev/null +++ b/.github/workflows/verification.yml @@ -0,0 +1,49 @@ +name: Package Verification + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + CARGO_TERM_COLOR: always + CARGO_BUILD_JOBS: "4" + +permissions: + contents: read + +jobs: + verification: + name: Verification + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust stable + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable + - name: Install Rust 1.85.0 + run: rustup toolchain install 1.85.0 + - name: Verify checkout compilation workloads + run: tools/verify_compile_workloads.sh + - name: Verify package contract + run: tools/verify_package.sh + - name: Upload verification artifacts + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: verification + path: | + target/verification/ + !target/verification/**/target/** + - name: Upload package archive + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: package-archive + path: target/package/*.crate diff --git a/.github/workflows/workflow-validation.yml b/.github/workflows/workflow-validation.yml new file mode 100644 index 0000000..6bb8c87 --- /dev/null +++ b/.github/workflows/workflow-validation.yml @@ -0,0 +1,42 @@ +name: Workflow validation + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + validate: + name: Workflow and harness validation + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install security and shell validators + uses: taiki-e/install-action@e407f7bafb71fd004bc5c2da3032e5470cbb6ef0 # v2.87.24 + with: + tool: shellcheck@0.11.0,zizmor@1.30.1 + fallback: none + - name: Install actionlint + run: | + curl --fail --location --max-time 120 --output "$RUNNER_TEMP/actionlint.tar.gz" \ + https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz + printf '%s %s\n' 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 \ + "$RUNNER_TEMP/actionlint.tar.gz" | sha256sum --check + tar --extract --gzip --file "$RUNNER_TEMP/actionlint.tar.gz" --directory "$RUNNER_TEMP" actionlint + - name: Validate workflows + run: '"$RUNNER_TEMP/actionlint"' + - name: Audit workflow security + run: zizmor --offline --strict-collection .github/workflows + - name: Check shell harnesses + run: shellcheck tools/*.sh + - name: Check Python syntax + run: python3 -B tools/verify_harness_syntax.py + - name: Test verification policies + run: python3 -B -m unittest discover -s tools/tests diff --git a/.gitignore b/.gitignore index 7d2b485..5c25314 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,4 @@ target # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ -build_rs_cov.profraw -cobertura.xml \ No newline at end of file +#.idea/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b0ac589..82d12d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -## Added in Unreleased +### `0.2.0` package contract -- `NestedTupleChain` trait for chaining two nested tuples together. -- `NestTupleRef` and `NestTupleMut` traits for converting flat tuples into nested tuples of references and mutable references, respectively. -- `NestedTupleFlattenOption` trait for flattening nested tuples of double options into nested tuples of options. +- One `tuplities` package provides recursive tuple utilities and flat tuple interoperability. +- The `flatten-nest` feature enables flat bridges and `alloc`. +- Flat widths are selected through the existing `size-N` features. +- `NestedTuple` provides structural membership and a compile-time element count. +- Recursive indexing and row access operate without flat tuple support or allocation. +- Declarative `neple!` and `neplety!` construct and name singleton-terminated lists. +- Recursive split, insert, remove, and linear-time reverse consume owned tuple nodes and preserve element types. +- The minimum supported Rust version is `1.85`. +- Public methods provide executable examples for result shapes, borrowed mutation, and conversion errors. +- `cargo llvm-cov` measures runtime targets and doctests. +- CI covers Rust `1.85.0`, allocator-free embedded builds, supported flat widths, mixed features, and bounded mutation analysis. +- `tools/verify_diesel_builders.sh` exercises isolated downstream SQLite consumers. +- `tools/verify_compile_workloads.sh` retains first-build and second-build reports for recursive and flat workloads. +- CI validates workflows and harnesses, verifies packaged MSRV consumers, and exercises pinned and moving-main downstream dependencies. +- Mutation grading rejects new survivors, stale equivalence reviews, incomplete reports, and tool failures while retaining raw results. +- `unsafe_code` is forbidden. ## [0.1.4] - 2025-12-15 diff --git a/Cargo.lock b/Cargo.lock index 3e741b1..9d1adf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,305 +2,15 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "proc-macro2" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "syn" -version = "2.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "tuplities" -version = "0.1.4" -dependencies = [ - "tuplities-clone", - "tuplities-copy", - "tuplities-debug", - "tuplities-default", - "tuplities-eq", - "tuplities-flatten-nest", - "tuplities-from", - "tuplities-hash", - "tuplities-index", - "tuplities-insert", - "tuplities-len", - "tuplities-mut", - "tuplities-option", - "tuplities-ord", - "tuplities-partial-eq", - "tuplities-partial-ord", - "tuplities-pop-back", - "tuplities-pop-front", - "tuplities-push-back", - "tuplities-push-front", - "tuplities-ref", - "tuplities-remove", - "tuplities-replicate", - "tuplities-reverse", - "tuplities-row", - "tuplities-split", - "tuplities-try-from", -] - -[[package]] -name = "tuplities-clone" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-copy" -version = "0.1.4" -dependencies = [ - "tuplities-clone", - "tuplities-derive", -] - -[[package]] -name = "tuplities-debug" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-default" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-derive" -version = "0.1.4" -dependencies = [ - "proc-macro2", - "quote", - "siphasher", - "syn", - "typenum", -] - -[[package]] -name = "tuplities-eq" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "tuplities-partial-eq", -] - -[[package]] -name = "tuplities-flatten-nest" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "tuplities-push-front", - "typenum", -] - -[[package]] -name = "tuplities-from" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-hash" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "tuplities-eq", -] - -[[package]] -name = "tuplities-index" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "tuplities-len", - "typenum", -] - -[[package]] -name = "tuplities-insert" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "typenum", -] - -[[package]] -name = "tuplities-len" -version = "0.1.4" +version = "0.2.0" dependencies = [ - "tuplities-derive", "typenum", ] -[[package]] -name = "tuplities-mut" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-option" -version = "0.1.4" -dependencies = [ - "tuplities-default", - "tuplities-derive", -] - -[[package]] -name = "tuplities-ord" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "tuplities-eq", - "tuplities-partial-ord", -] - -[[package]] -name = "tuplities-partial-eq" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-partial-ord" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "tuplities-partial-eq", -] - -[[package]] -name = "tuplities-pop-back" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-pop-front" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "tuplities-push-front", -] - -[[package]] -name = "tuplities-push-back" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-push-front" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-ref" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-remove" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "typenum", -] - -[[package]] -name = "tuplities-replicate" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-reverse" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - -[[package]] -name = "tuplities-row" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "tuplities-index", - "tuplities-len", - "tuplities-mut", - "tuplities-pop-back", - "tuplities-pop-front", - "tuplities-ref", - "typenum", -] - -[[package]] -name = "tuplities-split" -version = "0.1.4" -dependencies = [ - "tuplities-derive", - "typenum", -] - -[[package]] -name = "tuplities-try-from" -version = "0.1.4" -dependencies = [ - "tuplities-derive", -] - [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" diff --git a/Cargo.toml b/Cargo.toml index 57c937a..ebad560 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,84 +1,28 @@ [workspace] resolver = "2" -members = [ - "tuplities", - "tuplities-clone", - "tuplities-copy", - "tuplities-debug", - "tuplities-default", - "tuplities-eq", - "tuplities-flatten-nest", - "tuplities-from", - "tuplities-hash", - "tuplities-index", - "tuplities-insert", - "tuplities-len", - "tuplities-mut", - "tuplities-option", - "tuplities-ord", - "tuplities-partial-eq", - "tuplities-partial-ord", - "tuplities-pop-back", - "tuplities-pop-front", - "tuplities-push-back", - "tuplities-push-front", - "tuplities-ref", - "tuplities-remove", - "tuplities-replicate", - "tuplities-reverse", - "tuplities-row", - "tuplities-split", - "tuplities-try-from", - "tuplities-derive", -] +members = ["tuplities"] [workspace.package] authors = ["Luca Cappelletti"] edition = "2024" description = "A collection of utilities for working with tuples in Rust." license = "MIT" -license-file = "LICENSE" repository = "https://github.com/lucacappelletti94/tuplities" -homepage = "https://github.com/lucacappelletti94/tuplities" keywords = ["tuples", "utilities", "metaprogramming", "typenum"] categories = ["development-tools", "data-structures"] -version = "0.1.4" -readme = "README.md" +version = "0.2.0" rust-version = "1.85" [workspace.dependencies] -tuplities-clone = { path = "tuplities-clone", version = "0.1.4" } -tuplities-copy = { path = "tuplities-copy", version = "0.1.4" } -tuplities-debug = { path = "tuplities-debug", version = "0.1.4" } -tuplities-default = { path = "tuplities-default", version = "0.1.4" } -tuplities-eq = { path = "tuplities-eq", version = "0.1.4" } -tuplities-flatten-nest = { path = "tuplities-flatten-nest", version = "0.1.4" } -tuplities-from = { path = "tuplities-from", version = "0.1.4" } -tuplities-hash = { path = "tuplities-hash", version = "0.1.4" } -tuplities-index = { path = "tuplities-index", version = "0.1.4" } -tuplities-insert = { path = "tuplities-insert", version = "0.1.4" } -tuplities-len = { path = "tuplities-len", version = "0.1.4" } -tuplities-mut = { path = "tuplities-mut", version = "0.1.4" } -tuplities-option = { path = "tuplities-option", version = "0.1.4" } -tuplities-ord = { path = "tuplities-ord", version = "0.1.4" } -tuplities-partial-eq = { path = "tuplities-partial-eq", version = "0.1.4" } -tuplities-partial-ord = { path = "tuplities-partial-ord", version = "0.1.4" } -tuplities-pop-back = { path = "tuplities-pop-back", version = "0.1.4" } -tuplities-pop-front = { path = "tuplities-pop-front", version = "0.1.4" } -tuplities-push-back = { path = "tuplities-push-back", version = "0.1.4" } -tuplities-push-front = { path = "tuplities-push-front", version = "0.1.4" } -tuplities-ref = { path = "tuplities-ref", version = "0.1.4" } -tuplities-remove = { path = "tuplities-remove", version = "0.1.4" } -tuplities-replicate = { path = "tuplities-replicate", version = "0.1.4" } -tuplities-reverse = { path = "tuplities-reverse", version = "0.1.4" } -tuplities-row = { path = "tuplities-row", version = "0.1.4" } -tuplities-split = { path = "tuplities-split", version = "0.1.4" } -tuplities-try-from = { path = "tuplities-try-from", version = "0.1.4" } -tuplities-derive = { path = "tuplities-derive", version = "0.1.4" } typenum = { version = "1" } +[profile.mutants] +inherits = "test" +debug = "none" + [workspace.lints.rust] +unsafe_code = "forbid" missing_docs = "forbid" unused_macro_rules = "forbid" unconditional_recursion = "forbid" @@ -88,14 +32,9 @@ unused_must_use = "forbid" deprecated = "deny" [workspace.lints.clippy] -unreadable_literal = "allow" -missing_panics_doc = "allow" -new_ret_no_self = "allow" -struct_excessive_bools = "allow" -should_panic_without_expect = "allow" -doc_markdown = "allow" +missing_errors_doc = "forbid" +missing_panics_doc = "forbid" unnecessary_cast = "forbid" -unused_unit = "allow" redundant_clone = "forbid" clone_on_copy = "forbid" pedantic = { level = "deny", priority = -1 } diff --git a/README.md b/README.md index 983a47e..c04e4b3 100644 --- a/README.md +++ b/README.md @@ -1,290 +1,46 @@ # tuplities +[![Crates.io](https://img.shields.io/crates/v/tuplities.svg)](https://crates.io/crates/tuplities) [![Documentation](https://docs.rs/tuplities/badge.svg)](https://docs.rs/tuplities) [![CI](https://github.com/LucaCappelletti94/tuplities/workflows/Rust%20CI/badge.svg)](https://github.com/LucaCappelletti94/tuplities/actions) [![Security Audit](https://github.com/LucaCappelletti94/tuplities/workflows/Security%20Audit/badge.svg)](https://github.com/LucaCappelletti94/tuplities/actions) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![License MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Codecov](https://codecov.io/gh/LucaCappelletti94/tuplities/branch/main/graph/badge.svg)](https://codecov.io/gh/LucaCappelletti94/tuplities) -[![Downloads](https://img.shields.io/crates/d/tuplities.svg)](https://crates.io/crates/tuplities) -Tuple utilities in Rust, fractioned across several crates to improve compile times, providing several useful traits for both variadic programming with tuples as well as flat tuple manipulation. - -The main crate `tuplities` re-exports traits from subcrates in its `prelude` module, so that the subcrates may be compiled in parallel cutting down on overall build times. - -This library is `#[no_std]` compatible, making it suitable for embedded systems and other environments without the standard library. - -```toml -[dependencies] -tuplities = "0.1.4" -``` - -The library provides several traits for working with tuples: - -- [`TupleClone`](https://docs.rs/tuplities-clone/latest/tuplities_clone/trait.TupleClone.html): Provides a [`tuple_clone()`](https://docs.rs/tuplities-clone/latest/tuplities_clone/trait.TupleClone.html#tymethod.tuple_clone) method to clone tuples. All elements must implement `Clone`. -- [`TupleCopy`](https://docs.rs/tuplities-copy/latest/tuplities_copy/trait.TupleCopy.html): Provides a [`tuple_copy()`](https://docs.rs/tuplities-copy/latest/tuplities_copy/trait.TupleCopy.html#tymethod.tuple_copy) method to copy tuples. All elements must implement `Copy`. -- [`TupleDebug`](https://docs.rs/tuplities-debug/latest/tuplities_debug/trait.TupleDebug.html): Provides a [`tuple_debug()`](https://docs.rs/tuplities-debug/latest/tuplities_debug/trait.TupleDebug.html#tymethod.tuple_debug) method that returns a debug string representation of the tuple. All elements must implement `Debug`. -- [`TupleDefault`](https://docs.rs/tuplities-default/latest/tuplities_default/trait.TupleDefault.html): Provides a [`tuple_default()`](https://docs.rs/tuplities-default/latest/tuplities_default/trait.TupleDefault.html#tymethod.tuple_default) method to create default instances of tuples. All elements must implement `Default`. -- [`TupleReplicate`](https://docs.rs/tuplities-replicate/latest/tuplities_replicate/trait.TupleReplicate.html): Provides a [`tuple_replicate(value)`](https://docs.rs/tuplities-replicate/latest/tuplities_replicate/trait.TupleReplicate.html#tymethod.tuple_replicate) method to create tuples by replicating a single value across all positions. The value must implement `Clone` for tuples with 2+ elements, but not for empty tuples or single-element tuples. -- [`TupleHash`](https://docs.rs/tuplities-hash/latest/tuplities_hash/trait.TupleHash.html): Provides a [`tuple_hash()`](https://docs.rs/tuplities-hash/latest/tuplities_hash/trait.TupleHash.html#tymethod.tuple_hash) method to hash tuples with any hasher. All elements must implement `Hash`. -- [`TuplePartialEq`](https://docs.rs/tuplities-partial-eq/latest/tuplities_partial_eq/trait.TuplePartialEq.html): Provides a [`tuple_eq()`](https://docs.rs/tuplities-partial-eq/latest/tuplities_partial_eq/trait.TuplePartialEq.html#tymethod.tuple_eq) method to compare tuples for partial equality. All elements must implement `PartialEq`. -- [`TupleEq`](https://docs.rs/tuplities-eq/latest/tuplities_eq/trait.TupleEq.html): Provides a [`tuple_eq()`](https://docs.rs/tuplities-eq/latest/tuplities_eq/trait.TupleEq.html#tymethod.tuple_eq) method to compare tuples for total equality. All elements must implement `Eq`. -- [`TuplePartialOrd`](https://docs.rs/tuplities-partial-ord/latest/tuplities_partial_ord/trait.TuplePartialOrd.html): Provides a [`tuple_partial_cmp()`](https://docs.rs/tuplities-partial-ord/latest/tuplities_partial_ord/trait.TuplePartialOrd.html#tymethod.tuple_partial_cmp) method to compare tuples for partial ordering. All elements must implement `PartialOrd`. -- [`TupleOrd`](https://docs.rs/tuplities-ord/latest/tuplities_ord/trait.TupleOrd.html): Provides a [`tuple_cmp()`](https://docs.rs/tuplities-ord/latest/tuplities_ord/trait.TupleOrd.html#tymethod.tuple_cmp) method to compare tuples for total ordering. All elements must implement `Ord`. -- [`TupleOption`](https://docs.rs/tuplities-option/latest/tuplities_option/trait.TupleOption.html): Provides a [`transpose()`](https://docs.rs/tuplities-option/latest/tuplities_option/trait.TupleOption.html#tymethod.transpose) method to transpose a tuple of options into an option of a tuple. All elements must be `Option`. -- [`IntoTupleOption`](https://docs.rs/tuplities-option/latest/tuplities_option/trait.IntoTupleOption.html): Provides an [`into_options()`](https://docs.rs/tuplities-option/latest/tuplities_option/trait.IntoTupleOption.html#tymethod.into_options) method to convert a tuple into a tuple of options. -- [`NestedTupleOption`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOption.html): Provides a [`transpose()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOption.html#tymethod.transpose) method to transpose nested tuples of options into options of nested tuples. -- [`IntoNestedTupleOption`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.IntoNestedTupleOption.html): Provides an [`into_options()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.IntoNestedTupleOption.html#tymethod.into_options) method to convert nested tuples into nested tuples of options. -- [`NestedTupleFlattenOption`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleFlattenOption.html): Provides a [`flatten_options()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleFlattenOption.html#tymethod.flatten_options) method to flatten nested tuples of double options into nested tuples of options. -- [`NestedTupleOptionWith`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOptionWith.html): Provides `first_none_with`, `first_some_with`, and `transpose_or` helpers which operate with a parallel homogeneous nested tuple of `H` used for result/error mapping. -- [`NestedTupleTryFrom`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleTryFrom.html): Provides a [`nested_tuple_try_from()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleTryFrom.html#tymethod.nested_tuple_try_from) method to fallibly convert nested tuple types element-wise using `TryFrom` conversions. -- [`NestedTupleTryInto`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleTryInto.html): Provides a [`nested_tuple_try_into()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleTryInto.html#tymethod.nested_tuple_try_into) convenience method to perform fallible conversions using `NestedTupleTryFrom`. -- [`NestedTupleFrom`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleFrom.html): Provides an infallible [`nested_tuple_from()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleFrom.html#tymethod.nested_tuple_from) method to convert nested tuples using `From` element-wise. -- [`NestedTupleInto`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleInto.html): Provides an infallible [`nested_tuple_into()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleInto.html#tymethod.nested_tuple_into) convenience method to perform conversions using `NestedTupleFrom`. -- [`NestedTupleOptionFrom`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOptionFrom.html): Provides a [`nested_tuple_option_from()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOptionFrom.html#tymethod.nested_tuple_option_from) method to convert nested tuples of `Option`s using `From` element-wise. -- [`NestedTupleOptionInto`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOptionInto.html): Provides a [`nested_tuple_option_into()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOptionInto.html#tymethod.nested_tuple_option_into) convenience method to perform conversions using `NestedTupleOptionFrom`. -- [`NestedTupleOptionTryFrom`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOptionTryFrom.html): Provides a [`nested_tuple_option_try_from()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOptionTryFrom.html#tymethod.nested_tuple_option_try_from) method to fallibly convert nested tuples of `Option`s using `TryFrom` conversions. -- [`NestedTupleOptionTryInto`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOptionTryInto.html): Provides a [`nested_tuple_option_try_into()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleOptionTryInto.html#tymethod.nested_tuple_option_try_into) convenience method to perform fallible conversions using `NestedTupleOptionTryFrom`. -- [`NestedTupleReplicate`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleReplicate.html): Provides a [`nested_tuple_replicate(value)`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleReplicate.html#tymethod.nested_tuple_replicate) method to create nested tuples by replicating a single value across all positions. -- [`NestedTupleChain`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleChain.html): Provides a [`chain()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleChain.html#tymethod.chain) method to chain two nested tuples. -- [`TupleRef`](https://docs.rs/tuplities-ref/latest/tuplities_ref/trait.TupleRef.html): Provides a [`tuple_ref()`](https://docs.rs/tuplities-ref/latest/tuplities_ref/trait.TupleRef.html#tymethod.tuple_ref) method to get references to each element in the tuple. -- [`TupleMut`](https://docs.rs/tuplities-mut/latest/tuplities_mut/trait.TupleMut.html): Provides a [`tuple_mut()`](https://docs.rs/tuplities-mut/latest/tuplities_mut/trait.TupleMut.html#tymethod.tuple_mut) method to get mutable references to each element in the tuple. -- [`TupleRefMap`](https://docs.rs/tuplities-ref/latest/tuplities_ref/trait.TupleRefMap.html): Provides a [`tuple_ref_map()`](https://docs.rs/tuplities-ref/latest/tuplities_ref/trait.TupleRefMap.html#tymethod.tuple_ref_map) method to apply `TupleRef` to each element of a tuple of tuples, returning a tuple of tuples of references. -- [`TupleMutMap`](https://docs.rs/tuplities-mut/latest/tuplities_mut/trait.TupleMutMap.html): Provides a [`tuple_mut_map()`](https://docs.rs/tuplities-mut/latest/tuplities_mut/trait.TupleMutMap.html#tymethod.tuple_mut_map) method to apply `TupleMut` to each element of a tuple of tuples, returning a tuple of tuples of mutable references. -- [`TuplePopFront`](https://docs.rs/tuplities-pop-front/latest/tuplities_pop_front/trait.TuplePopFront.html): Provides a [`pop_front()`](https://docs.rs/tuplities-pop-front/latest/tuplities_pop_front/trait.TuplePopFront.html#tymethod.pop_front) method to remove and return the first element of the tuple along with the remaining elements as a new tuple. -- [`TuplePopBack`](https://docs.rs/tuplities-pop-back/latest/tuplities_pop_back/trait.TuplePopBack.html): Provides a [`pop_back()`](https://docs.rs/tuplities-pop-back/latest/tuplities_pop_back/trait.TuplePopBack.html#tymethod.pop_back) method to remove and return the last element of the tuple along with the remaining elements as a new tuple. -- [`TuplePushFront`](https://docs.rs/tuplities-push-front/latest/tuplities_push_front/trait.TuplePushFront.html): Provides a [`push_front(element)`](https://docs.rs/tuplities-push-front/latest/tuplities_push_front/trait.TuplePushFront.html#tymethod.push_front) method to add an element to the front of the tuple, returning a new tuple. -- [`TuplePushBack`](https://docs.rs/tuplities-push-back/latest/tuplities_push_back/trait.TuplePushBack.html): Provides a [`push_back(element)`](https://docs.rs/tuplities-push-back/latest/tuplities_push_back/trait.TuplePushBack.html#tymethod.push_back) method to add an element to the back of the tuple, returning a new tuple. -- [`TupleRemove`](https://docs.rs/tuplities-remove/latest/tuplities_remove/trait.TupleRemove.html): Provides a [`remove()`](https://docs.rs/tuplities-remove/latest/tuplities_remove/trait.TupleRemove.html#tymethod.remove) method to remove and return the element at the specified index [`typenum`](https://docs.rs/typenum/latest/typenum/)'s `Idx` of the tuple along with the remaining elements as a new tuple. -- [`TupleInsert`](https://docs.rs/tuplities-insert/latest/tuplities_insert/trait.TupleInsert.html): Provides an [`insert()`](https://docs.rs/tuplities-insert/latest/tuplities_insert/trait.TupleInsert.html#tymethod.insert) method to insert an element at the specified index [`typenum`](https://docs.rs/typenum/latest/typenum/)'s `Idx` into the tuple, returning the tuple with the element inserted. -- [`TupleSplit`](https://docs.rs/tuplities-split/latest/tuplities_split/trait.TupleSplit.html): Provides a [`split()`](https://docs.rs/tuplities-split/latest/tuplities_split/trait.TupleSplit.html#tymethod.split) method to split a tuple at the specified compile-time index [`typenum`](https://docs.rs/typenum/latest/typenum/)'s `Idx`, returning two tuples containing the elements before and at/after the index. -- [`TupleReverse`](https://docs.rs/tuplities-reverse/latest/tuplities_reverse/trait.TupleReverse.html): Provides a [`reverse()`](https://docs.rs/tuplities-reverse/latest/tuplities_reverse/trait.TupleReverse.html#tymethod.reverse) method to reverse the order of elements in a tuple. -- [`TupleTryFrom`](https://docs.rs/tuplities-try-from/latest/tuplities_try_from/trait.TupleTryFrom.html): Provides a [`tuple_try_from()`](https://docs.rs/tuplities-try-from/latest/tuplities_try_from/trait.TupleTryFrom.html#tymethod.tuple_try_from) method to fallibly convert from other types into tuples. -- [`TupleTryInto`](https://docs.rs/tuplities-try-from/latest/tuplities_try_from/trait.TupleTryInto.html): Provides a [`tuple_try_into()`](https://docs.rs/tuplities-try-from/latest/tuplities_try_from/trait.TupleTryInto.html#tymethod.tuple_try_into) method to fallibly convert tuples into other types. -- [`TupleFrom`](https://docs.rs/tuplities-from/latest/tuplities_from/trait.TupleFrom.html): Provides a [`tuple_from()`](https://docs.rs/tuplities-from/latest/tuplities_from/trait.TupleFrom.html#tymethod.tuple_from) method to infallibly convert from other types into tuples. -- [`TupleInto`](https://docs.rs/tuplities-from/latest/tuplities_from/trait.TupleInto.html): Provides a [`tuple_into()`](https://docs.rs/tuplities-from/latest/tuplities_from/trait.TupleInto.html#tymethod.tuple_into) method to infallibly convert tuples into other types. -- [`FlattenNestedTuple`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.FlattenNestedTuple.html): Provides a [`flatten()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.FlattenNestedTuple.html#tymethod.flatten) method to convert nested tuples like `(A, (B, (C,)))` into flat tuples like `(A, B, C)`. -- [`NestTuple`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestTuple.html): Provides a [`nest()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestTuple.html#tymethod.nest) method to convert flat tuples like `(A, B, C)` into nested tuples like `(A, (B, (C,)))`. -- [`NestedTupleIndex`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleIndex.html): Provides a [`nested_index()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleIndex.html#tymethod.nested_index) method to access elements at flat indices in nested tuples using [`typenum`](https://docs.rs/typenum/latest/typenum/)'s `Idx`. -- [`NestedTupleIndexMut`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleIndexMut.html): Provides a [`nested_index_mut()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleIndexMut.html#tymethod.nested_index_mut) method to access mutable elements at flat indices in nested tuples using [`typenum`](https://docs.rs/typenum/latest/typenum/)'s `Idx`. -- [`NestedTupleRef`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleRef.html): Provides a [`nested_tuple_ref()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleRef.html#tymethod.nested_tuple_ref) method to get nested references to each leaf value of a nested tuple. -- [`NestedTupleMut`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleMut.html): Provides a [`nested_tuple_mut()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleMut.html#tymethod.nested_tuple_mut) method to get nested mutable references to each leaf value of a nested tuple. -- [`NestedTuplePopFront`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTuplePopFront.html): Provides a [`nested_pop_front()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTuplePopFront.html#tymethod.nested_pop_front) method to pop the front element of nested tuples. -- [`NestedTuplePopBack`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTuplePopBack.html): Provides a [`nested_pop_back()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTuplePopBack.html#tymethod.nested_pop_back) method to pop the back element of nested tuples. -- [`NestedTuplePushFront`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTuplePushFront.html): Provides a [`nested_push_front(element)`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTuplePushFront.html#tymethod.nested_push_front) method to push an element to the front of a nested tuple. -- [`NestedTuplePushBack`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTuplePushBack.html): Provides a [`nested_push_back(element)`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTuplePushBack.html#tymethod.nested_push_back) method to push an element to the back of a nested tuple. -- [`NestedTupleRow`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleRow.html): Provides a [`nested_tuple_row()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleRow.html#tymethod.nested_tuple_row) method to access elements at the specified index across a nested tuple-of-tuples, returning a nested row tuple by value. -- [`NestedTupleRow`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleRow.html): Provides a [`nested_tuple_row()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleRow.html#tymethod.nested_tuple_row) method to access elements at the specified index across a nested tuple-of-tuples, returning a nested row tuple of references (analogous to `TupleRow`). -- [`NestedTupleRowMut`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleRowMut.html): Provides [`nested_tuple_row_mut()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleRowMut.html#tymethod.nested_tuple_row_mut) for mutable nested row access. -- [`NestedTupleStartsWith`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestedTupleStartsWith.html): A marker trait for compile-time verification that a nested tuple starts with the same types as another nested tuple. For example, `(A, (B, (C,)))` implements `NestedTupleStartsWith<(A,)>` and `NestedTupleStartsWith<(A, (B,))>`. -- [`NestTupleMatrix`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestTupleMatrix.html): Provides a [`nest_matrix()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.NestTupleMatrix.html#tymethod.nest_matrix) method to convert flat tuples of flat tuples like `((A, B), (C, D))` into nested tuples of nested tuples like `((A, (B,)), ((C, (D,)),))`. -- [`FlattenNestedTupleMatrix`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.FlattenNestedTupleMatrix.html): Provides a [`flatten_matrix()`](https://docs.rs/tuplities-flatten-nest/latest/tuplities_flatten_nest/trait.FlattenNestedTupleMatrix.html#tymethod.flatten_matrix) method to convert nested tuples of nested tuples back into flat tuples of flat tuples. -- [`TupleLen`](https://docs.rs/tuplities-len/latest/tuplities_len/trait.TupleLen.html): Provides the length of the tuple as a compile-time [`typenum::Unsigned`](https://docs.rs/typenum/latest/typenum/marker_traits/trait.Unsigned.html) type. -- [`UnitTuple`](https://docs.rs/tuplities-len/latest/tuplities_len/trait.UnitTuple.html): A marker trait implemented for empty tuples `()` with `TupleLen`. -- [`SingletonTuple`](https://docs.rs/tuplities-len/latest/tuplities_len/trait.SingletonTuple.html): A marker trait implemented for single-element tuples `(T,)` with `TupleLen`. -- [`PairTuple`](https://docs.rs/tuplities-len/latest/tuplities_len/trait.PairTuple.html): A marker trait implemented for two-element tuples `(T1, T2)` with `TupleLen`. -- [`TupleIndex`](https://docs.rs/tuplities-index/latest/tuplities_index/trait.TupleIndex.html): Provides an [`index()`](https://docs.rs/tuplities-index/latest/tuplities_index/trait.TupleIndex.html#tymethod.index) method to access the element at the specified index [`typenum`](https://docs.rs/typenum/latest/typenum/)'s `Idx` of the tuple. -- [`TupleIndexMut`](https://docs.rs/tuplities-index/latest/tuplities_index/trait.TupleIndexMut.html): Provides an [`index_mut()`](https://docs.rs/tuplities-index/latest/tuplities_index/trait.TupleIndexMut.html#tymethod.index_mut) method to access a mutable reference to the element at the specified index [`typenum`](https://docs.rs/typenum/latest/typenum/)'s `Idx` of the tuple. -- [`FirstTupleIndex`](https://docs.rs/tuplities-index/latest/tuplities_index/trait.FirstTupleIndex.html): A convenience trait providing a [`first_tuple_index()`](https://docs.rs/tuplities-index/latest/tuplities_index/trait.FirstTupleIndex.html#tymethod.first_tuple_index) method to access the first element of a tuple. -- [`LastTupleIndex`](https://docs.rs/tuplities-index/latest/tuplities_index/trait.LastTupleIndex.html): A convenience trait providing a [`last_tuple_index()`](https://docs.rs/tuplities-index/latest/tuplities_index/trait.LastTupleIndex.html#tymethod.last_tuple_index) method to access the last element of a tuple. -- [`TupleRow`](https://docs.rs/tuplities-row/latest/tuplities_row/trait.TupleRow.html): Provides a [`tuple_row()`](https://docs.rs/tuplities-row/latest/tuplities_row/trait.TupleRow.html#tymethod.tuple_row) method to access elements at the specified index across all tuples in a tuple of tuples (row-wise indexing). -- [`TupleRowMut`](https://docs.rs/tuplities-row/latest/tuplities_row/trait.TupleRowMut.html): Provides a [`tuple_row_mut()`](https://docs.rs/tuplities-row/latest/tuplities_row/trait.TupleRowMut.html#tymethod.tuple_row_mut) method to access mutable elements at the specified index across all tuples in a tuple of tuples (mutable row-wise indexing). -- [`FirstTupleRow`](https://docs.rs/tuplities-row/latest/tuplities_row/trait.FirstTupleRow.html): A convenience trait providing a [`first_tuple_row()`](https://docs.rs/tuplities-row/latest/tuplities_row/trait.FirstTupleRow.html#tymethod.first_tuple_row) method to access the first element of each tuple in a tuple of tuples. -- [`LastTupleRow`](https://docs.rs/tuplities-row/latest/tuplities_row/trait.LastTupleRow.html): A convenience trait providing a [`last_tuple_row()`](https://docs.rs/tuplities-row/latest/tuplities_row/trait.LastTupleRow.html#tymethod.last_tuple_row) method to access the last element of each tuple in a tuple of tuples. - -## Variadic Programming with Nested Tuples - -One of the most powerful features of `tuplities` is the ability to perform variadic programming on tuples of arbitrary length by converting them to a nested representation `(Head, (Tail...))`. This allows you to define traits recursively without needing to implement them for every possible tuple size using macros. - -It is important to note that "true" variadic generics are not yet available in Rust. As such, there is still a limit on how large the flat tuples can be, determined by which `size-XX` feature flags are enabled in this crate. However, this nested approach nevertheless allows for an arbitrary level of recursion while maintaining performant compile times, typically in the order of a few seconds even for large tuple sizes. - -Here is an example of how to define a trait that works on tuples of any size where all elements implement `Display`: +Recursive heterogeneous tuple utilities for `no_std` Rust. Use `neple!` to construct lists and `neplety!` to name their types. Traits and macros are available at the crate root and through `tuplities::prelude`. ```rust use tuplities::prelude::*; -use core::fmt::Display; - -// 1. Define flat & nested traits -trait PrintTuple { - fn print_tuple(&self); -} -trait PrintNested { - fn print_nested(&self); -} -// 2. Implement for the base cases -impl PrintNested for () { - fn print_nested(&self) {} -} -impl PrintNested for (Head,) { - fn print_nested(&self) { - println!("{}", self.0); - } -} - -// 3. Implement for the recursive case -impl PrintNested for (Head, Tail) -where - Head: Display, // Current constraint - Tail: PrintNested, // Recursive constraint -{ - fn print_nested(&self) { - println!("{}", self.0); - self.1.print_nested(); - } -} - -// 4. Blanket implementation for nestable tuples -// whose nested form implements PrintNested -impl PrintTuple for T -where - for<'a> &'a T: NestTuple, -{ - fn print_tuple(&self) { - self.nest().print_nested(); - } -} - -// Usage -let tuple = (42, "hello", true); -tuple.print_tuple(); -let another_tuple = (); -another_tuple.print_tuple(); -let yet_another_tuple = ("only one element",); -yet_another_tuple.print_tuple(); +type Values = neplety!(u8, &'static str, bool); +let values: Values = neple!(1_u8, "two").chain(neple!(true)); +assert_eq!(values, (1, ("two", (true,)))); +assert_eq!(values.into_options().transpose(), Some(values)); +assert!(*NestedTupleIndex::::nested_index(&values)); +assert_eq!(Values::LEN, 3); + +let (prefix, suffix) = NestedTupleSplit::::nested_split(values); +let inserted = NestedTupleInsert::::nested_insert(prefix.chain(suffix), 42_u16); +let (removed, remainder) = NestedTupleRemove::::nested_remove(inserted); +assert_eq!(removed, 42); +assert_eq!(remainder.nested_reverse(), neple!(true, "two", 1_u8)); ``` -This pattern is extensively used in libraries like [`diesel-builders`](https://github.com/LucaCappelletti94/diesel-builders/) to build complex, type-safe abstractions over tuples of varying sizes. - -### Comparison with Macro-based Approach - -Without nested tuples, achieving the same result typically requires defining a recursive macro that implements the trait for every tuple size up to a maximum limit. This approach has several downsides: +Split preserves its suffix and singleton-terminates its prefix. Insertion and removal preserve untouched suffixes and singleton-terminate reconstructed empty tails, while reversal produces a singleton-terminated list. Consuming operations accept reference-valued elements and leave borrowed recursive tails untouched, with traversal into borrowed tails rejected. -1. **Code Bloat**: The macro generates a separate implementation for each tuple size, leading to a large amount of generated code. -2. **Compile Times & Quadratic Bounds Growth**: The compiler has to process all these implementations, which significantly increases compile times. The number of trait bounds generated increases quadratically with the maximum tuple size `N` (proportional to `N^2`), as the macro generates implementations for all sizes from 0 to `N`, and size `k` requires `k` bounds. -3. **Maintenance**: Writing and maintaining complex macros can be error-prone and difficult to debug. -4. **Code Coverage**: Code coverage tools often struggle to provide meaningful information for code generated via macros, making it harder to ensure your variadic logic is fully tested. - -Here is how the same trait would be implemented using macros: +The default `flatten-nest` feature provides flat/nested tuple and matrix conversions and enables `alloc`. Flat conversions support widths through 8, with `size-16`, `size-32`, `size-48`, `size-64`, `size-96`, and `size-128` selecting larger limits. Enabling multiple size features selects the largest limit. ```rust -use core::fmt::Display; - -trait PrintTuple { - fn print_tuple(&self); -} - -macro_rules! impl_print_tuple { - // Base case: empty tuple - () => { - impl PrintTuple for () { - fn print_tuple(&self) {} - } - }; - // Recursive step: implement for (Head, Tail...) and recurse - ($Head:ident $(, $Tail:ident)*) => { - impl<$Head: Display, $($Tail: Display),*> PrintTuple for ($Head, $($Tail,)*) { - fn print_tuple(&self) { - #[allow(non_snake_case)] - let ($Head, $($Tail,)*) = self; - println!("{}", $Head); - $(println!("{}", $Tail);)* - } - } - - // Recurse for smaller tuple size - impl_print_tuple!($($Tail),*); - }; -} - -// Invoke once for the maximum size you want to support (e.g., 12) -impl_print_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); +# #[cfg(feature = "flatten-nest")] { +use tuplities::{FlattenNestedTuple, NestTuple}; + +let flat = (String::from("one"), 2_u8, true); +let nested = flat.nest(); +assert_eq!(nested, (String::from("one"), (2, (true,)))); +assert_eq!(nested.flatten(), (String::from("one"), 2, true)); +# } ``` -## Features - -The crate provides optional features to enable specific traits. All features are enabled by default for convenience, but can be selectively disabled to reduce compile time and binary size. - -### Trait Features - -The following features enable individual trait crates: - -- `clone`: Enables `TupleClone` trait -- `copy`: Enables `TupleCopy` trait -- `debug`: Enables `TupleDebug` trait -- `tuple-default`: Enables `TupleDefault` trait -- `eq`: Enables `TupleEq` trait -- `flatten-nest`: Enables `FlattenNestedTuple`, `NestTuple`, `NestedTupleIndex`, `NestedTupleIndexMut`, `NestedTupleTryFrom`, `NestedTupleTryInto`, `NestTupleMatrix`, `FlattenNestedTupleMatrix`, `NestedTupleOption`, `IntoNestedTupleOption`, and `NestedTupleOptionWith` traits -- `from`: Enables `TupleFrom` and `TupleInto` traits -- `hash`: Enables `TupleHash` trait -- `mut`: Enables `TupleMut` and `TupleMutMap` traits -- `option`: Enables `TupleOption` and `IntoTupleOption` traits -- `ord`: Enables `TupleOrd` trait -- `partial-eq`: Enables `TuplePartialEq` trait -- `partial-ord`: Enables `TuplePartialOrd` trait -- `remove`: Enables `TupleRemove` trait -- `insert`: Enables `TupleInsert` trait -- `len`: Enables `TupleLen`, `UnitTuple`, `SingletonTuple`, and `PairTuple` traits -- `index`: Enables `TupleIndex`, `TupleIndexMut`, `FirstTupleIndex`, and `LastTupleIndex` traits -- `pop-front`: Enables `TuplePopFront`, `TupleRefFront`, and `TupleMutFront` traits -- `pop-back`: Enables `TuplePopBack`, `TupleRefBack`, and `TupleMutBack` traits -- `push-front`: Enables `TuplePushFront` trait -- `push-back`: Enables `TuplePushBack` trait -- `ref`: Enables `TupleRef` and `TupleRefMap` traits -- `replicate`: Enables `TupleReplicate` trait -- `reverse`: Enables `TupleReverse` trait -- `row`: Enables `TupleRow`, `TupleRowMut`, `FirstTupleRow`, and `LastTupleRow` traits -- `split`: Enables `TupleSplit` trait -- `try-from`: Enables `TupleTryFrom` and `TupleTryInto` traits - -### Size Features - -Additionally, the crate provides features to generate trait implementations for tuples up to different sizes: 8 (default), 16, 32, 48, 64, 96, or 128 elements. Use the `size-XX` features to enable larger tuple support. - -```toml -[dependencies] -tuplities = { version = "0.1.4", default-features = false, features = ["clone", "index", "size-32"] } -``` - -## Performance - -Compile times scale with tuple size due to code generation. Below are measured build times for different maximum tuple sizes captured via the project's `measure_compile_times.sh` script on a typical development machine. Values are approximate and represent wall-clock `real` times. If you only plan to use variadic nested tuples, you may prefer to use only the `tuplities-flatten-nest` crate, which has significantly lower compile times. - -### tuplities (root crate) - -| Max Tuple Size | Compile Time | -|----------------|--------------| -| 8 (default) | ~4.07s | -| 16 | ~2.71s | -| 32 | ~3.29s | -| 48 | ~4.55s | -| 64 | ~6.83s | -| 96 | ~15.75s | -| 128 | ~32.44s | - -### tuplities-flatten-nest (flatten-nest crate only) - -| Max Tuple Size | Compile Time | -|----------------|--------------| -| 8 (default) | ~3.18s | -| 16 | ~2.45s | -| 32 | ~2.60s | -| 48 | ~2.86s | -| 64 | ~3.40s | -| 96 | ~5.46s | -| 128 | ~9.52s | - -## Architecture - -The project is split into multiple crates for improved compile times: - -- `tuplities/`: Main crate that re-exports traits from subcrates -- `tuplities-{trait_name}/`: Individual crates providing specific traits -- `tuplities-derive/`: Procedural macro crate that generates trait implementations - -## License - -This project is licensed under the MIT License. See the LICENSE file for details. - -## Contribution +Disable default features for the allocator-free recursive core and enable `alloc` for `NestedTupleIntoVec`. Indexing, row access, and `NestedTuple::LEN` depend on recursive structure without flat arity limits. Both singleton-terminated and unit-terminated lists support these operations, including borrowed tails. -Contributions are welcome! Please open issues or pull requests on the [GitHub repository](https://github.com/lucacappelletti94/tuplities). +Rust `1.85` or later is required. Standard traits such as `Clone`, `Copy`, `Debug`, equality, ordering, and hashing compose through nested pairs without additional tuple traits. diff --git a/measure_compile_times.sh b/measure_compile_times.sh deleted file mode 100755 index 9bc6753..0000000 --- a/measure_compile_times.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# Script to measure compile times for different tuple size features -# Usage: ./measure_compile_times.sh - -set -e - -echo "Measuring compile times for different tuple sizes..." -echo "==================================================" - -# Array of features to test (empty string means default/size-8) -FEATURES=("" "size-16" "size-32" "size-48" "size-64" "size-96" "size-128") - -# Test each feature -echo "Measuring compile times for tuplities crate..." -for feature in "${FEATURES[@]}"; do - if [ -z "$feature" ]; then - echo "Testing default (size-8):" - time (cargo clean && cargo build --quiet) - else - echo "Testing $feature:" - time (cargo clean && cargo build --quiet --features "$feature") - fi - echo "" -done - -# Test each feature for the `tuplities-flatten-nest` crate -echo "Measuring compile times for tuplities-flatten-nest crate..." -for feature in "${FEATURES[@]}"; do - if [ -z "$feature" ]; then - echo "Testing default (size-8):" - time (cd tuplities-flatten-nest && cargo clean && cargo build --quiet) - else - echo "Testing $feature:" - time (cd tuplities-flatten-nest && cargo clean && cargo build --quiet --features "$feature") - fi - echo "" -done - -echo "All measurements completed!" -echo "" -echo "Update the README.md table with the new compile times." -echo "The format should be:" -echo "| Max Tuple Size | Compile Time |" -echo "|----------------|--------------|" -echo "| 8 (default) | ~Xs |" -echo "| 16 | ~Xs |" -echo "| etc... | ~Xs |" \ No newline at end of file diff --git a/tools/check_mutants.py b/tools/check_mutants.py new file mode 100644 index 0000000..cf6562a --- /dev/null +++ b/tools/check_mutants.py @@ -0,0 +1,304 @@ +"""Grade complete mutation runs against source-bound reviewed equivalents.""" + +import argparse +import hashlib +import json +import sys +from pathlib import Path + +REVIEWED_FILENAME = Path(".cargo") / "mutants-reviewed-equivalents.json" +ALLOWED_EXIT_CODES = (0, 2) +BASELINE_SCENARIO = "Baseline" +SUMMARY_FIELDS = { + "CaughtMutant": "caught", + "MissedMutant": "missed", + "Timeout": "timeout", + "Unviable": "unviable", +} +HEX_DIGITS = set("0123456789abcdef") +EXIT_OK = 0 +EXIT_FAILED = 1 + + +def _sha256(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _is_count(value): + return isinstance(value, int) and not isinstance(value, bool) + + +def _is_hash(value): + return isinstance(value, str) and len(value) == 64 and set(value) <= HEX_DIGITS + + +def _function_label(mutant): + function = mutant.get("function") + if not isinstance(function, dict): + return None + name = function.get("function_name") + if not isinstance(name, str) or not name: + return None + return_type = function.get("return_type") + if isinstance(return_type, str) and return_type: + return f"{name} {return_type}" + return name + + +def _load_json(path, problems, label): + try: + raw = path.read_text(encoding="utf-8") + except OSError as exc: + problems.append(f"cannot read {label} {path}: {exc}") + return None + try: + data = json.loads(raw) + except json.JSONDecodeError as exc: + problems.append(f"corrupt {label} {path}: invalid JSON ({exc.msg} at line {exc.lineno} column {exc.colno})") + return None + if not isinstance(data, dict): + problems.append(f"corrupt {label} {path}: top level is not a JSON object") + return None + return data + + +def _grade_baseline(outcome, problems): + if outcome.get("summary") != "Success": + problems.append(f"baseline failed (summary {outcome.get('summary')!r}), so the run is not a normal complete run") + return + phases = outcome.get("phase_results") + if not isinstance(phases, list) or not phases: + problems.append("incomplete report: baseline has no phase results") + return + if [phase.get("phase") if isinstance(phase, dict) else None for phase in phases] != ["Build", "Test"]: + problems.append("incomplete report: baseline must complete Build and Test") + for phase in phases: + if not isinstance(phase, dict) or phase.get("process_status") != "Success": + name = phase.get("phase") if isinstance(phase, dict) else None + status = phase.get("process_status") if isinstance(phase, dict) else None + problems.append(f"baseline phase {name!r} did not succeed (status {status!r})") + +def _failed_status(status): + return ( + isinstance(status, dict) + and len(status) == 1 + and next(iter(status)) in ("Failure", "Signalled") + and _is_count(next(iter(status.values()))) + and next(iter(status.values())) > 0 + ) + + +def _grade_mutant_phases(outcome, name, problems): + summary = outcome.get("summary") + if summary not in ("MissedMutant", "CaughtMutant", "Unviable"): + return + phases = outcome.get("phase_results") + expected = ["Build"] if summary == "Unviable" else ["Build", "Test"] + if ( + not isinstance(phases, list) + or any(not isinstance(phase, dict) for phase in phases) + or [phase.get("phase") for phase in phases] != expected + ): + problems.append(f"incomplete phase results for mutant {name}") + return + statuses = [phase.get("process_status") for phase in phases] + if summary == "Unviable": + valid = _failed_status(statuses[0]) + elif summary == "CaughtMutant": + valid = statuses[0] == "Success" and _failed_status(statuses[1]) + else: + valid = statuses == ["Success", "Success"] + if not valid: + problems.append(f"phase results disagree with {summary} for mutant {name}") + + +def _grade_report(report, problems): + outcomes = report.get("outcomes") + if not isinstance(outcomes, list): + problems.append("incomplete report: outcomes is missing or not a list") + return None, [] + baselines = [] + mutants = [] + seen = set() + for index, outcome in enumerate(outcomes): + if not isinstance(outcome, dict): + problems.append(f"malformed outcome {index}: not a JSON object") + continue + if outcome.get("scenario") == BASELINE_SCENARIO: + baselines.append(outcome) + continue + scenario = outcome.get("scenario") + if not isinstance(scenario, dict) or not isinstance(scenario.get("Mutant"), dict): + problems.append(f"malformed outcome {index}: unrecognized scenario {scenario!r}") + continue + mutant = scenario["Mutant"] + name = mutant.get("name") + if not isinstance(name, str) or not name: + problems.append(f"malformed outcome {index}: mutant name is missing") + continue + file = mutant.get("file") + label = _function_label(mutant) + replacement = mutant.get("replacement") + if ( + not isinstance(file, str) or not file + or "function" not in mutant + or (mutant["function"] is not None and label is None) + or not isinstance(replacement, str) + ): + problems.append(f"malformed mutant {name}: invalid file, function or replacement") + continue + if name in seen: + problems.append(f"duplicate outcome: {name}") + continue + seen.add(name) + _grade_mutant_phases(outcome, name, problems) + mutants.append((name, file, label, replacement, outcome.get("summary"))) + if len(baselines) != 1: + problems.append(f"incomplete report: {len(baselines)} baseline outcome(s) found, expected exactly 1") + else: + _grade_baseline(baselines[0], problems) + + counts = {field: 0 for field in SUMMARY_FIELDS.values()} + for name, _, _, _, summary in mutants: + field = SUMMARY_FIELDS.get(summary) + if field is None: + problems.append(f"unrecognized summary {summary!r} for mutant {name}") + continue + counts[field] += 1 + + for field in ("missed", "caught", "timeout", "unviable"): + reported = report.get(field) + if not _is_count(reported): + problems.append(f"corrupt report: {field} total is missing or not an integer") + elif reported != counts[field]: + problems.append(f"totals disagree: {field} is {reported} but {counts[field]} mutant outcome(s) have that summary") + total = report.get("total_mutants") + if not _is_count(total): + problems.append("corrupt report: total_mutants is missing or not an integer") + else: + if total != len(mutants): + problems.append(f"totals disagree: total_mutants is {total} but {len(mutants)} mutant outcome(s) are present") + if total != sum(counts.values()): + problems.append(f"totals disagree: total_mutants is {total} but the recognized summaries sum to {sum(counts.values())}") + if counts["timeout"]: + problems.append(f"incomplete run: {counts['timeout']} mutant(s) timed out. Only complete runs are accepted") + + missed = [ + (name, file, label, replacement) + for name, file, label, replacement, summary in mutants + if summary == "MissedMutant" + ] + return counts, missed + + +def _grade_records(reviewed, repo_root, problems): + records = reviewed.get("records") + if not isinstance(records, list): + problems.append("corrupt reviewed records: records is missing or not a list") + return {} + index = {} + for position, record in enumerate(records): + if not isinstance(record, dict): + problems.append(f"malformed reviewed record {position}: not a JSON object") + continue + file = record.get("file") + function = record.get("function") + replacement = record.get("replacement") + digest = record.get("sha256") + reason = record.get("reason") + if ( + not isinstance(file, str) or not file + or not isinstance(function, str) or not function + or not isinstance(replacement, str) or not replacement + or not _is_hash(digest) + or not isinstance(reason, str) or not reason.strip() + ): + problems.append(f"malformed reviewed record {position}: file, function, replacement, sha256 or reason is missing") + continue + key = (file, function, replacement) + if key in index: + problems.append(f"duplicate reviewed record: {file} {function} {replacement}") + continue + source = repo_root / file + if not source.is_file(): + index[key] = (False, f"source file {file} is missing") + else: + fresh = _sha256(source) == digest + index[key] = (fresh, "source no longer matches the reviewed hash" if not fresh else None) + for key, (fresh, detail) in index.items(): + if not fresh: + problems.append(f"stale equivalence record: {key[0]} {key[1]} {key[2]} ({detail})") + return index + + +def _match_missed(missed, records, problems): + accepted = [] + for name, file, label, replacement in missed: + entry = records.get((file, label, replacement)) + if entry is None: + problems.append(f"unreviewed missed mutant: {name}") + elif entry[0]: + accepted.append(name) + else: + problems.append(f"missed mutant covered only by a stale record: {name}") + return accepted + + +def _parse_args(argv): + parser = argparse.ArgumentParser(description="Grade a cargo-mutants run against reviewed mutant equivalents.") + parser.add_argument("raw_outcomes", type=Path, help="cargo-mutants JSON outcomes report") + parser.add_argument("--exit-code", type=int, required=True, metavar="N", help="cargo-mutants exit code") + parser.add_argument( + "--reviewed", + type=Path, + default=None, + help=f"reviewed records JSON (default {REVIEWED_FILENAME} under the repo root)", + ) + parser.add_argument( + "--repo-root", + type=Path, + default=Path(__file__).resolve().parent.parent, + help="repo root that record file paths resolve against", + ) + return parser.parse_args(argv) + + +def main(argv=None): + args = _parse_args(argv) + repo_root = args.repo_root + reviewed_path = repo_root / REVIEWED_FILENAME if args.reviewed is None else repo_root / args.reviewed + problems = [] + if args.exit_code not in ALLOWED_EXIT_CODES: + problems.append(f"cargo-mutants exit code {args.exit_code} is not a normal complete run (expected 0 or 2)") + report = _load_json(args.raw_outcomes, problems, "mutation report") + reviewed = _load_json(reviewed_path, problems, "reviewed records") + records = _grade_records(reviewed, repo_root, problems) if reviewed is not None else None + counts = None + accepted = [] + if report is not None: + counts, missed = _grade_report(report, problems) + if counts is not None and args.exit_code in ALLOWED_EXIT_CODES: + expected_exit = 2 if counts["missed"] else 0 + if args.exit_code != expected_exit: + problems.append(f"cargo-mutants exit code {args.exit_code} disagrees with missed={counts['missed']}") + if records is not None: + accepted = _match_missed(missed, records, problems) + if problems: + print(f"mutant grading: FAILED ({len(problems)} problem(s))") + for problem in problems: + print(f" problem: {problem}") + else: + print("mutant grading: ok") + if counts is not None: + total = counts["missed"] + counts["caught"] + counts["timeout"] + counts["unviable"] + print( + f" total={total} caught={counts['caught']} unviable={counts['unviable']} " + f"timeout={counts['timeout']} missed={counts['missed']}" + ) + for name in accepted: + print(f" accepted: {name}") + return EXIT_OK if not problems else EXIT_FAILED + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/tests/test_check_mutants.py b/tools/tests/test_check_mutants.py new file mode 100644 index 0000000..7651700 --- /dev/null +++ b/tools/tests/test_check_mutants.py @@ -0,0 +1,347 @@ +import contextlib +import hashlib +import importlib.util +import io +import json +import tempfile +import unittest +from pathlib import Path + +TOOLS_DIR = Path(__file__).resolve().parent.parent +GRADER_PATH = TOOLS_DIR / "check_mutants.py" + +EQUIVALENTS = [ + ( + "tuplities/src/nested_into_vec.rs", + " for ()>::into_vec", + "-> Vec", + "vec![]", + ), + ( + "tuplities/src/nested_option.rs", + "::transpose", + "-> Option", + "Some(Default::default())", + ), + ( + "tuplities/src/nested_option.rs", + " for ()>::transpose_or", + "-> Result", + "Ok(Default::default())", + ), + ( + "tuplities/src/nested_option_try_from.rs", + " for ()>::nested_tuple_option_try_from", + "-> Result", + "Ok(Default::default())", + ), + ( + "tuplities/src/nested_try_from.rs", + " for ()>::nested_tuple_try_from", + "-> Result", + "Ok(Default::default())", + ), +] + +FRESH_KEYS = [ + ( + "tuplities/src/nested_into_vec.rs", + " for (T,)>::into_vec", + "-> Vec", + "vec![]", + ), + ( + "tuplities/src/nested_try_from.rs", + " for (Head,)>::nested_tuple_try_from", + "-> Result", + "Ok(Default::default())", + ), +] + +UNREVIEWED_KEY = ( + "tuplities/src/nested_option.rs", + ",)>::transpose", + "-> Option", + "Some(Default::default())", +) + +SUMMARY_FIELDS = { + "CaughtMutant": "caught", + "MissedMutant": "missed", + "Timeout": "timeout", + "Unviable": "unviable", +} + +SPAN = {"start": {"line": 1, "column": 1}, "end": {"line": 2, "column": 2}} + + +def _load_grader(): + spec = importlib.util.spec_from_file_location("check_mutants", GRADER_PATH) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _baseline(summary="Success", phases=None): + if phases is None: + phases = [ + {"phase": "Build", "duration": 1.0, "process_status": "Success", "argv": ["cargo", "test", "--no-run"]}, + {"phase": "Test", "duration": 1.0, "process_status": "Success", "argv": ["cargo", "test"]}, + ] + return { + "scenario": "Baseline", + "summary": summary, + "log_path": "log/baseline.log", + "diff_path": None, + "phase_results": phases, + } + + +def _mutant(file, function_name, return_type, replacement, summary, line=1): + name = f"{file}:{line}:1: replace {function_name} {return_type} with {replacement}" + phases = _baseline()["phase_results"] + if summary == "Unviable": + phases = [{**phases[0], "process_status": {"Failure": 101}}] + elif summary == "CaughtMutant": + phases[1]["process_status"] = {"Failure": 101} + return { + "scenario": { + "Mutant": { + "name": name, + "package": "tuplities", + "file": file, + "function": {"function_name": function_name, "return_type": return_type, "span": SPAN}, + "span": SPAN, + "replacement": replacement, + "genre": "FnValue", + } + }, + "summary": summary, + "log_path": f"log/mutant-{line}.log", + "diff_path": None, + "phase_results": phases, + } + + +def _report(outcomes): + totals = {field: 0 for field in SUMMARY_FIELDS.values()} + for outcome in outcomes: + if outcome["scenario"] == "Baseline": + continue + field = SUMMARY_FIELDS.get(outcome["summary"]) + if field is not None: + totals[field] += 1 + return { + "outcomes": outcomes, + "total_mutants": sum(totals.values()), + **totals, + "success": 0, + "start_time": "2026-10-03T23:17:21.057444822Z", + "end_time": "2026-10-03T23:21:03.596964341Z", + "cargo_mutants_version": "27.1.0", + } + + +def _make_repo(root): + """Create the source files for the reviewed records and a fresh review file.""" + for file, _, _, _ in EQUIVALENTS: + path = root / file + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("fn fixture() -> u8 { 1 }\n", encoding="utf-8") + records = [] + for file, function_name, return_type, replacement in EQUIVALENTS: + records.append( + { + "file": file, + "function": f"{function_name} {return_type}", + "replacement": replacement, + "sha256": hashlib.sha256((root / file).read_bytes()).hexdigest(), + "reason": "the replacement builds the same value the unit-tuple impl returns", + } + ) + reviewed = root / "reviewed.json" + reviewed.write_text(json.dumps({"records": records}, indent=2), encoding="utf-8") + return reviewed + + +class GraderTests(unittest.TestCase): + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + self.addCleanup(self._tmp.cleanup) + self.root = Path(self._tmp.name) + self.reviewed = _make_repo(self.root) + self.raw = self.root / "outcomes.json" + self.grader = _load_grader() + + def write_outcomes(self, outcomes): + self.raw.write_text(json.dumps(_report(outcomes)), encoding="utf-8") + + def _run(self, exit_code=2): + argv = [str(self.raw), "--exit-code", str(exit_code), "--reviewed", str(self.reviewed), "--repo-root", str(self.root)] + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + code = self.grader.main(argv) + return code, buffer.getvalue() + + def _accepted_outcomes(self): + outcomes = [_baseline()] + for line, key in enumerate(EQUIVALENTS, start=1): + outcomes.append(_mutant(*key, "MissedMutant", line)) + for line, key in enumerate(FRESH_KEYS, start=10): + outcomes.append(_mutant(*key, "CaughtMutant", line)) + for line, key in enumerate(FRESH_KEYS, start=20): + outcomes.append(_mutant(*key, "Unviable", line)) + return outcomes + + def test_known_equivalents_accepted(self): + self.write_outcomes(self._accepted_outcomes()) + code, out = self._run() + self.assertEqual(0, code) + + def test_fresh_missed_rejected(self): + outcomes = self._accepted_outcomes() + outcomes.append(_mutant(*UNREVIEWED_KEY, "MissedMutant", 30)) + self.write_outcomes(outcomes) + code, out = self._run() + self.assertNotEqual(0, code) + self.assertIn(UNREVIEWED_KEY[1], out) + + def test_changed_source_invalidates_acceptance(self): + self.write_outcomes(self._accepted_outcomes()) + path = self.root / "tuplities" / "src" / "nested_option.rs" + path.write_text(path.read_text(encoding="utf-8") + "// touched\n", encoding="utf-8") + code, out = self._run() + self.assertNotEqual(0, code) + + def test_timeout_rejected(self): + outcomes = [_baseline(), _mutant(*FRESH_KEYS[0], "Timeout", 1), _mutant(*FRESH_KEYS[1], "CaughtMutant", 2)] + self.write_outcomes(outcomes) + code, out = self._run(exit_code=2) + self.assertNotEqual(0, code) + + def test_baseline_failure_rejected(self): + failed = _baseline( + summary="Failure", + phases=[{"phase": "Build", "duration": 1.0, "process_status": "Failure", "argv": ["cargo", "test", "--no-run"]}], + ) + self.write_outcomes([failed, _mutant(*FRESH_KEYS[0], "CaughtMutant", 1)]) + code, out = self._run() + self.assertNotEqual(0, code) + + def test_tool_exit_code_rejected(self): + self.write_outcomes(self._accepted_outcomes()) + code, out = self._run(exit_code=1) + self.assertNotEqual(0, code) + + def test_corrupt_report_rejected(self): + self.raw.write_text("{ not json", encoding="utf-8") + code, out = self._run() + self.assertNotEqual(0, code) + + def test_incomplete_report_rejected(self): + self.write_outcomes(self._accepted_outcomes()) + data = json.loads(self.raw.read_text(encoding="utf-8")) + data["missed"] = data["missed"] - 1 + self.raw.write_text(json.dumps(data), encoding="utf-8") + code, out = self._run() + self.assertNotEqual(0, code) + + def test_missing_baseline_rejected(self): + self.write_outcomes(self._accepted_outcomes()) + data = json.loads(self.raw.read_text(encoding="utf-8")) + data["outcomes"] = data["outcomes"][1:] + self.raw.write_text(json.dumps(data), encoding="utf-8") + code, out = self._run() + self.assertNotEqual(0, code) + + def test_duplicate_reviewed_records_rejected(self): + self.write_outcomes(self._accepted_outcomes()) + data = json.loads(self.reviewed.read_text(encoding="utf-8")) + data["records"].append(dict(data["records"][0])) + self.reviewed.write_text(json.dumps(data), encoding="utf-8") + code, out = self._run() + self.assertNotEqual(0, code) + + def test_duplicate_outcome_rejected(self): + outcomes = self._accepted_outcomes() + duplicate = _mutant(*FRESH_KEYS[0], "CaughtMutant", 40) + outcomes.append(duplicate) + outcomes.append(duplicate) + self.write_outcomes(outcomes) + code, out = self._run() + self.assertNotEqual(0, code) + + def test_known_equivalent_absent_ok(self): + outcomes = [_baseline()] + for line, key in enumerate(FRESH_KEYS, start=1): + outcomes.append(_mutant(*key, "CaughtMutant", line)) + for line, key in enumerate(FRESH_KEYS, start=10): + outcomes.append(_mutant(*key, "Unviable", line)) + self.write_outcomes(outcomes) + code, out = self._run(exit_code=0) + self.assertEqual(0, code) + + def test_stale_record_without_survivor_rejected(self): + outcomes = [_baseline()] + for line, key in enumerate(FRESH_KEYS, start=1): + outcomes.append(_mutant(*key, "CaughtMutant", line)) + for line, key in enumerate(FRESH_KEYS, start=10): + outcomes.append(_mutant(*key, "Unviable", line)) + self.write_outcomes(outcomes) + path = self.root / "tuplities" / "src" / "nested_option.rs" + path.write_text(path.read_text(encoding="utf-8") + "// touched\n", encoding="utf-8") + code, out = self._run(exit_code=0) + self.assertNotEqual(0, code) + + def test_malformed_reviewed_record_rejected(self): + self.write_outcomes(self._accepted_outcomes()) + data = json.loads(self.reviewed.read_text(encoding="utf-8")) + data["records"][0]["reason"] = "" + data["records"][1]["sha256"] = "zzz" + self.reviewed.write_text(json.dumps(data), encoding="utf-8") + code, out = self._run() + self.assertNotEqual(0, code) + + def test_unrecognized_summary_rejected(self): + outcomes = [_baseline()] + for line, key in enumerate(FRESH_KEYS, start=1): + outcomes.append(_mutant(*key, "CaughtMutant", line)) + outcomes.append(_mutant(*UNREVIEWED_KEY, "Bogus", 30)) + self.write_outcomes(outcomes) + code, out = self._run() + self.assertNotEqual(0, code) + + def test_baseline_without_test_phase_rejected(self): + outcomes = self._accepted_outcomes() + outcomes[0]["phase_results"] = outcomes[0]["phase_results"][:1] + self.write_outcomes(outcomes) + code, _ = self._run() + self.assertNotEqual(0, code) + + def test_mutant_without_completed_phases_rejected(self): + outcomes = self._accepted_outcomes() + del outcomes[1]["phase_results"] + self.write_outcomes(outcomes) + code, _ = self._run() + self.assertNotEqual(0, code) + + def test_exit_code_disagrees_with_survivors_rejected(self): + self.write_outcomes(self._accepted_outcomes()) + code, _ = self._run(exit_code=0) + self.assertNotEqual(0, code) + + def test_caught_nonfunction_and_deletion_mutants_accepted(self): + operator = _mutant(*FRESH_KEYS[0], "CaughtMutant") + operator["scenario"]["Mutant"]["function"] = None + operator["scenario"]["Mutant"]["genre"] = "BinaryOperator" + operator["scenario"]["Mutant"]["replacement"] = "-" + deletion = _mutant(*FRESH_KEYS[1], "CaughtMutant") + deletion["scenario"]["Mutant"]["genre"] = "MatchArm" + deletion["scenario"]["Mutant"]["replacement"] = "" + self.write_outcomes([_baseline(), operator, deletion]) + code, _ = self._run(exit_code=0) + self.assertEqual(0, code) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_diesel_builders_revisions.py b/tools/tests/test_diesel_builders_revisions.py new file mode 100644 index 0000000..423f3c1 --- /dev/null +++ b/tools/tests/test_diesel_builders_revisions.py @@ -0,0 +1,61 @@ +import os +import subprocess +import tempfile +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +SCRIPT = REPO_ROOT / "tools" / "verify_diesel_builders.sh" + +PINNED_BUILDERS_REV = "7041ce661200fcf644d44c63e15695d880737945" +PINNED_DIESEL_REV = "81f1cdb2927f6d0ca11039c16bca2ebd2c117be2" + + +class DieselBuildersRevisionTests(unittest.TestCase): + def run_script(self, env_updates): + directory = tempfile.TemporaryDirectory() + self.addCleanup(directory.cleanup) + env = dict(os.environ) + env["CARGO"] = str(Path(directory.name) / "absent-cargo") + env.pop("DIESEL_BUILDERS_REV", None) + env.pop("DIESEL_REV", None) + env.pop("TUPLITIES_PACKAGE_DIR", None) + env.update(env_updates) + proc = subprocess.run( + ["bash", str(SCRIPT)], + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=10, + ) + return proc + + def assert_rejected(self, env_updates): + proc = self.run_script(env_updates) + self.assertEqual(proc.returncode, 2, proc.stderr) + + def test_builders_revision_without_diesel_revision_is_rejected(self): + self.assert_rejected({"DIESEL_BUILDERS_REV": PINNED_BUILDERS_REV}) + + def test_diesel_revision_without_builders_revision_is_rejected(self): + self.assert_rejected({"DIESEL_REV": PINNED_DIESEL_REV}) + + def test_short_revision_is_rejected(self): + self.assert_rejected( + {"DIESEL_BUILDERS_REV": "abc123", "DIESEL_REV": PINNED_DIESEL_REV}, + ) + + def test_uppercase_revision_is_rejected(self): + self.assert_rejected( + { + "DIESEL_BUILDERS_REV": PINNED_BUILDERS_REV.upper(), + "DIESEL_REV": PINNED_DIESEL_REV, + }, + ) + + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_harness_syntax.py b/tools/tests/test_harness_syntax.py new file mode 100644 index 0000000..a567122 --- /dev/null +++ b/tools/tests/test_harness_syntax.py @@ -0,0 +1,45 @@ +import importlib.util +import tempfile +import unittest +from pathlib import Path + + +SPEC = importlib.util.spec_from_file_location( + "verify_harness_syntax", Path(__file__).parents[1] / "verify_harness_syntax.py" +) +MODULE = importlib.util.module_from_spec(SPEC) + + +class HarnessSyntaxTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + SPEC.loader.exec_module(MODULE) + + def check_source(self, source, suffix=".sh"): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / ("harness" + suffix) + path.write_text(source) + MODULE.check(path) + + def test_valid_quoted_python_blocks(self): + self.check_source( + "python3 - <<'PY'\nvalue = 1\nPY\n" + 'python3 - <<"PYTHON"\nvalue = 2\nPYTHON\n' + ) + + def test_invalid_embedded_python_reports_original_line(self): + with self.assertRaises(SyntaxError) as failure: + self.check_source("#!/bin/sh\npython3 - <<'PY'\nif:\nPY\n") + self.assertEqual(failure.exception.lineno, 3) + + def test_unterminated_python_block_is_rejected(self): + with self.assertRaises(ValueError): + self.check_source("python3 - <<'PY'\nvalue = 1\n") + + def test_invalid_python_file_is_rejected(self): + with self.assertRaises(SyntaxError): + self.check_source("if:\n", ".py") + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_package_failures.py b/tools/tests/test_package_failures.py new file mode 100644 index 0000000..0d2045e --- /dev/null +++ b/tools/tests/test_package_failures.py @@ -0,0 +1,40 @@ +import json +import os +import subprocess +import tempfile +import unittest +from pathlib import Path + + +SCRIPT = Path(__file__).resolve().parents[1] / "verify_package.sh" + + +class PackageFailureTests(unittest.TestCase): + def test_missing_source_records_failed_step(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = root / "missing-source" + diagnostics = root / "diagnostics" + result = subprocess.run( + ["sh", str(SCRIPT), "--allow-dirty"], + env={ + **os.environ, + "TUPLITIES_PACKAGE_DIR": str(source), + "TUPLITIES_VERIFICATION_DIR": str(diagnostics), + }, + capture_output=True, + text=True, + timeout=15, + ) + self.assertEqual(result.returncode, 1) + summary_path, = diagnostics.glob("run-*/summary.json") + summary = json.loads(summary_path.read_text()) + self.assertIsNotNone(summary["failure"]) + self.assertEqual( + [(step["step"], step["status"]) for step in summary["steps"]], + [("checkout-manifest", "failed")], + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/verify_compile_workloads.sh b/tools/verify_compile_workloads.sh new file mode 100755 index 0000000..afcbc6c --- /dev/null +++ b/tools/verify_compile_workloads.sh @@ -0,0 +1,349 @@ +#!/bin/sh +set -eu + +root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) + +exec python3 -B - "$root" <<'PY' +import json +import os +import shlex +import shutil +import signal +import subprocess +import sys +import tempfile +import time +from datetime import datetime, timezone +from pathlib import Path + +ROOT = Path(sys.argv[1]).resolve() +BUILD_TIMEOUT = 1200 +META_TIMEOUT = 120 +RUN_TIMEOUT = 60 +VERSION_TIMEOUT = 30 + + +def resolve(path): + path = Path(path) + return path if path.is_absolute() else (ROOT / path).resolve() + + +PACKAGE_DIR = resolve(os.environ.get("TUPLITIES_PACKAGE_DIR", str(ROOT / "tuplities"))) +OUTPUT_ROOT = resolve( + os.environ.get( + "TUPLITIES_VERIFICATION_DIR", + str(ROOT / "target" / "verification" / "compile-workloads"), + ) +) +CARGO = shlex.split(os.environ.get("CARGO", "cargo")) + +CASES = [ + ("recursive8", 8, [], None), + ("recursive32", 32, [], None), + ("recursive64", 64, [], None), + ("recursive128", 128, [], None), + ("flat8", 8, ["flatten-nest"], 8), + ("flat32", 32, ["flatten-nest", "size-32"], 32), + ("flat128", 128, ["flatten-nest", "size-128"], 128), +] + + +class CaseFailure(Exception): + pass + + +def nested_literal(values): + out = f"({values[-1]},)" + for value in reversed(values[:-1]): + out = f"({value}, {out})" + return out + + +def generate_main(case, width, flat_width): + values = [1000 + 7 * i for i in range(width)] + option_values = [20000 + 13 * i for i in range(width)] + flat_values = [30000 + 11 * i for i in range(flat_width)] if flat_width else [] + middle = width // 2 + split_at = width // 2 + remove_at = width // 4 + insert_value = 900000 + mutated_value = 800000 + mutated = list(values) + mutated[middle] = mutated_value + inserted_values = values[:1] + [insert_value] + values[1:] + removed_values = values[:remove_at] + values[remove_at + 1:] + u32 = lambda count: ", ".join(["u32"] * count) + lines = [ + '#![recursion_limit = "512"]', + "use tuplities::prelude::*;", + "", + "fn main() {", + f" type List = neplety!({u32(width)});", + f" assert_eq!(List::LEN, {width});", + f" let mut list: List = neple!({', '.join(map(str, values))});", + "", + f" assert_eq!(*NestedTupleIndex::::nested_index(&list), {values[0]});", + f" assert_eq!(*NestedTupleIndex::::nested_index(&list), {values[middle]});", + f" assert_eq!(*NestedTupleIndex::::nested_index(&list), {values[-1]});", + "", + f" type Options = neplety!({', '.join(['Option'] * width)});", + f" let options: Options = neple!({', '.join(f'Some({v})' for v in option_values)});", + " let transposed: Option = options.transpose();", + f" assert_eq!(transposed, Some({nested_literal(option_values)}));", + "", + f" let (prefix, suffix): (neplety!({u32(split_at)}), neplety!({u32(width - split_at)})) =", + f" NestedTupleSplit::::nested_split(list);", + f" assert_eq!(prefix, {nested_literal(values[:split_at])});", + f" assert_eq!(suffix, {nested_literal(values[split_at:])});", + "", + f" let inserted = NestedTupleInsert::::nested_insert(list, {insert_value});", + f" assert_eq!(inserted, {nested_literal(inserted_values)});", + "", + f" let (removed, remainder) = NestedTupleRemove::::nested_remove(list);", + f" assert_eq!(removed, {values[remove_at]});", + f" assert_eq!(remainder, {nested_literal(removed_values)});", + "", + " let reversed = NestedTupleReverse::nested_reverse(list);", + f" assert_eq!(reversed, {nested_literal(values[::-1])});", + "", + f" *NestedTupleIndexMut::::nested_index_mut(&mut list) = {mutated_value};", + f" assert_eq!(*NestedTupleIndex::::nested_index(&list), {mutated_value});", + " let final_reversed = NestedTupleReverse::nested_reverse(list);", + f" assert_eq!(final_reversed, {nested_literal(mutated[::-1])});", + ] + scenarios = ( + "construction,indexing-shared,indexing-mutable,option-transpose," + "split,insert,remove,reverse" + ) + if flat_width: + flat_csv = ", ".join(map(str, flat_values)) + lines += [ + "", + f" let flat = ({flat_csv});", + " let round_trip = flat.nest().flatten();", + ] + lines += [f" assert_eq!(round_trip.{index}, {value});" for index, value in enumerate(flat_values)] + scenarios += f",flat-roundtrip-{flat_width}" + lines += [ + "", + f' println!("compile-workload case={case} width={width} scenarios={scenarios} status=ok");', + "}", + "", + ] + return "\n".join(lines), scenarios + + +def generate_manifest(case, package_dir, features): + extra = "" + if features: + extra = ", features = [" + ", ".join(f'"{feature}"' for feature in features) + "]" + return ( + "[package]\n" + f'name = "workload-{case}"\n' + 'version = "0.0.0"\n' + 'edition = "2021"\n' + "publish = false\n" + "\n" + "[workspace]\n" + "\n" + "[dependencies]\n" + f'tuplities = {{ path = "{package_dir}", default-features = false{extra} }}\n' + 'typenum = "1"\n' + ) + + +def execute(cmd, cwd, env, timeout): + command = [str(item) for item in cmd] + with subprocess.Popen(command, cwd=str(cwd), env=env, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, text=True, start_new_session=True) as process: + try: + out, err = process.communicate(timeout=timeout) + except subprocess.TimeoutExpired: + os.killpg(process.pid, signal.SIGKILL) + out, err = process.communicate(timeout=30) + raise subprocess.TimeoutExpired(command, timeout, output=out, stderr=err) from None + return subprocess.CompletedProcess(command, process.returncode, out, err) + + +def run_logged(cmd, cwd, env, log_path, identity, timeout): + started = datetime.now(timezone.utc).isoformat() + header = ( + f"identity={identity}\n" + f"command={shlex.join(map(str, cmd))}\n" + f"started={started}\n" + f"timeout={timeout}s\n" + f"cwd={cwd}\n" + "--- output ---\n" + ) + start = time.monotonic() + try: + completed = execute(cmd, cwd, env, timeout) + output = completed.stdout + completed.stderr + exit_code = completed.returncode + except subprocess.TimeoutExpired as exc: + output = "".join( + part + for part in (exc.stdout or "", exc.stderr or "") + if isinstance(part, str) + ) + exit_code = -1 + duration = time.monotonic() - start + with open(log_path, "a") as handle: + handle.write(header + output + "--- summary ---\n") + handle.write(f"duration={duration:.2f}s exit={exit_code}\n") + return exit_code + + +def capture(cmd, cwd, env, timeout): + completed = execute(cmd, cwd, env, timeout) + if completed.returncode != 0: + raise CaseFailure(f"command failed: {shlex.join(map(str, cmd))}\n{completed.stderr}") + return completed.stdout + + +def record_versions(case_dir, packages): + env = os.environ + cargo_version = capture(CARGO + ["--version"], case_dir, env, VERSION_TIMEOUT).strip() + rustc = ["rustc", "--version"] + if len(CARGO) > 1 and CARGO[1].startswith("+"): + rustc = ["rustup", "run", CARGO[1][1:], *rustc] + rustc_version = capture(rustc, case_dir, env, VERSION_TIMEOUT).strip() + lines = [ + f"cargo={cargo_version}", + f"rustc={rustc_version}", + ] + for name in sorted(packages): + package = packages[name] + lines.append( + f"package {name} version={package['version']} " + f"rust_version={package.get('rust_version') or 'unspecified'}" + ) + for key in ("CARGO", "RUSTC_WRAPPER", "CARGO_BUILD_RUSTC_WRAPPER"): + if key in env: + lines.append(f"{key}={env[key]}") + (case_dir / "versions.txt").write_text("\n".join(lines) + "\n") + + +def run_case(case, width, features, flat_width, run_dir, temp_base): + src_dir = temp_base / case + (src_dir / "src").mkdir(parents=True) + main_source, scenarios = generate_main(case, width, flat_width) + manifest = generate_manifest(case, PACKAGE_DIR, features) + (src_dir / "Cargo.toml").write_text(manifest) + (src_dir / "src" / "main.rs").write_text(main_source) + + case_dir = run_dir / case + case_dir.mkdir(parents=True) + (case_dir / "generated").mkdir() + shutil.copy2(src_dir / "Cargo.toml", case_dir / "generated" / "Cargo.toml") + shutil.copy2(src_dir / "src" / "main.rs", case_dir / "generated" / "main.rs") + target_dir = case_dir / "target" + env = os.environ + + (case_dir / "config.json").write_text( + json.dumps( + { + "case": case, + "width": width, + "features": features, + "flat_width": flat_width, + "package_dir": str(PACKAGE_DIR), + "scenarios": scenarios, + }, + indent=2, + ) + + "\n" + ) + + built_package_ids = set() + for build_index in (1, 2): + cmd = ["nice", "-n", "10", *CARGO, "build", "--release", "--timings", "--message-format=json-render-diagnostics", "-j", "4"] + if build_index == 2: + cmd.append("--locked") + build_env = {**env, "CARGO_TARGET_DIR": str(target_dir)} + identity = f"{run_dir.name}/{case}/build{build_index}" + exit_code = run_logged( + cmd, src_dir, build_env, case_dir / f"build{build_index}.log", identity, BUILD_TIMEOUT + ) + if exit_code != 0: + raise CaseFailure(f"build {build_index} exit={exit_code}, log={case_dir / f'build{build_index}.log'}") + shutil.copytree(target_dir / "cargo-timings", case_dir / "timings" / f"build{build_index}") + for line in (case_dir / f"build{build_index}.log").read_text().splitlines(): + if line.startswith("{"): + event = json.loads(line) + if event.get("reason") == "compiler-artifact": + built_package_ids.add(event["package_id"]) + + metadata_text = capture( + [*CARGO, "metadata", "--format-version", "1", "--frozen"], + src_dir, env, META_TIMEOUT + ) + metadata = json.loads(metadata_text) + (case_dir / "metadata.json").write_text(metadata_text) + packages = {package["name"]: package for package in metadata["packages"]} + expected_packages = {"workload-" + case, "tuplities", "typenum"} + if set(packages) != expected_packages: + raise CaseFailure(f"resolved packages {sorted(packages)} != {sorted(expected_packages)}") + + dependency_tree = capture([*CARGO, "tree", "--frozen"], src_dir, env, META_TIMEOUT) + feature_tree = capture([*CARGO, "tree", "--frozen", "-e", "features"], src_dir, env, META_TIMEOUT) + (case_dir / "dependency-tree.txt").write_text(dependency_tree) + (case_dir / "feature-tree.txt").write_text(feature_tree) + if features: + if "flatten-nest" not in feature_tree or "alloc" not in feature_tree: + raise CaseFailure("feature tree lacks flatten-nest/alloc") + if flat_width in (32, 128) and f"size-{flat_width}" not in feature_tree: + raise CaseFailure(f"feature tree lacks size-{flat_width}") + else: + for feature in ("flatten-nest", "alloc", "size-"): + if feature in feature_tree: + raise CaseFailure(f"recursive case activated feature {feature}") + + package_names = {package["id"]: package["name"] for package in metadata["packages"]} + unit_names = {package_names[package_id] for package_id in built_package_ids} + if unit_names != expected_packages: + raise CaseFailure(f"built packages {sorted(unit_names)} != {sorted(expected_packages)}") + (case_dir / "built-packages.json").write_text(json.dumps(sorted(unit_names)) + "\n") + + record_versions(case_dir, packages) + + executable = target_dir / "release" / f"workload-{case}" + expected_line = f"compile-workload case={case} width={width} scenarios={scenarios} status=ok" + exit_code = run_logged( + [executable], src_dir, env, case_dir / "run.log", f"{run_dir.name}/{case}/run", RUN_TIMEOUT + ) + if exit_code != 0: + raise CaseFailure(f"executable exit={exit_code}, log={case_dir / 'run.log'}") + stdout = (case_dir / "run.log").read_text().split("--- output ---\n", 1)[1] + stdout = stdout.split("--- summary ---\n", 1)[0].strip() + if stdout != expected_line: + raise CaseFailure(f"stdout {stdout!r} != expected {expected_line!r}") + + +def main(): + if not PACKAGE_DIR.is_dir(): + print(f"tuplities package dir missing: {PACKAGE_DIR}", file=sys.stderr) + return 1 + run_dir = OUTPUT_ROOT / (datetime.now(timezone.utc).strftime("run-%Y%m%d-%H%M%S-pid") + str(os.getpid())) + run_dir.mkdir(parents=True) + temp_base = Path(tempfile.mkdtemp(prefix="tuplities-compile-workloads-")) + failures = [] + try: + for case, width, features, flat_width in CASES: + try: + run_case(case, width, features, flat_width, run_dir, temp_base) + print(f"ok {case}") + except CaseFailure as failure: + failures.append(case) + print(f"FAILED {case}: {failure}", file=sys.stderr) + finally: + shutil.rmtree(temp_base) + print(f"run dir: {run_dir}") + if failures: + print(f"failed cases: {', '.join(failures)}", file=sys.stderr) + return 1 + return 0 + + +sys.exit(main()) +PY diff --git a/tools/verify_diesel_builders.sh b/tools/verify_diesel_builders.sh new file mode 100755 index 0000000..6a149bc --- /dev/null +++ b/tools/verify_diesel_builders.sh @@ -0,0 +1,604 @@ +#!/usr/bin/env bash +# Verify diesel-builders at git main, or at an explicit paired revision, against the local tuplities checkout. +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +root="$(cd -- "${script_dir}/.." && pwd)" +package_dir="${TUPLITIES_PACKAGE_DIR:-${root}/tuplities}" +cargo_bin="${CARGO:-cargo}" + +if [[ ! -f "${package_dir}/Cargo.toml" ]]; then + echo "error: tuplities manifest not found at ${package_dir}/Cargo.toml" >&2 + exit 1 +fi + +run_id="$(date -u +%Y%m%dT%H%M%SZ)-$$" +log_dir="${root}/target/verification/diesel-builders/${run_id}" +work_dir="$(mktemp -d "${TMPDIR:-/tmp}/tuplities-diesel-builders-verify.XXXXXX")" +cleanup() { rm -rf "${work_dir}"; } +trap cleanup EXIT +mkdir -p "${log_dir}" + +python3 - "${root}" "${package_dir}" "${work_dir}" "${log_dir}" "${cargo_bin}" "${run_id}" <<'PY' +import json +import os +import re +import signal +import subprocess +import sys +from datetime import datetime, timezone + +ROOT, PKG_DIR, WORK_DIR, LOG_DIR, CARGO, RUN_ID = sys.argv[1:7] + +# Subprocess timeouts in seconds. Every cargo invocation carries one. +TIMEOUTS = {"version": 30, "update": 900, "metadata": 120, "check": 1500, "run": 1500} + +# Paired revisions pin the builder source and the shared Diesel lockfile entry. +REV_ENV = { + "diesel-builders": "DIESEL_BUILDERS_REV", + "diesel": "DIESEL_REV", +} +REV_RE = re.compile(r"^[0-9a-f]{40}$") +SOURCE_URLS = { + "diesel-builders": "https://github.com/LucaCappelletti94/diesel-builders", + "diesel": "https://github.com/diesel-rs/diesel", +} + +# Two consumer configurations: the consumer's defaults, and no defaults with +# size-32. Each entry carries the extra feature options its configuration adds. +CONFIGS = [ + ("default", ""), + ("no-default-size32", 'default-features = false, features = ["size-32"]'), +] + +# Every scenario the smoke executable must reach. The harness fails if a +# marker is absent, so an omitted scenario cannot pass silently. +MARKERS = [ + "defaults", + "missing_email_error_identity", + "setters_insert_load", + "index_reads", + "sql_query_construction", + "find_after_mutation", + "upsert", + "ancestor_bundle", + "ancestor_read", + "delete", +] + + +ENV = {**os.environ, "CARGO_TERM_COLOR": "never"} + + +class StepFailure(Exception): + def __init__(self, message, log_path): + super().__init__(message) + self.log_path = log_path + + +class UsageError(Exception): + pass + + +def utcnow(): + return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + +def write_log(path, text): + with open(path, "w") as fh: + fh.write(text) + + +def run_cargo(config, step, args, cwd, log_path): + cmd = ["nice", "-n", "10", CARGO, *args] + if step in ("check", "run"): + # --locked builds the exact resolution the metadata check saw. + cmd += ["-j", "4", "--locked"] + header = ( + f"run={RUN_ID} config={config} step={step} started={utcnow()}\n" + f"$ {' '.join(cmd)}\n" + ) + timeout = TIMEOUTS[step] + with subprocess.Popen( + cmd, + cwd=cwd, + env=ENV, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + start_new_session=True, + ) as proc: + try: + out, err = proc.communicate(timeout=timeout) + except subprocess.TimeoutExpired: + os.killpg(os.getpgid(proc.pid), signal.SIGKILL) + out, err = proc.communicate(timeout=30) + write_log( + log_path, + header + f"TIMED OUT after {timeout}s\n" + out + err, + ) + raise StepFailure(f"step {step} timed out after {timeout}s", log_path) from None + status = "ok" if proc.returncode == 0 else f"failed exit_code={proc.returncode}" + write_log(log_path, header + out + err + f"--- ended={utcnow()} status={status} ---\n") + if proc.returncode != 0: + raise StepFailure(f"step {step} failed with exit code {proc.returncode}", log_path) + return subprocess.CompletedProcess(cmd, proc.returncode, out, err) + + +def run_versioned(label, cmd): + proc = subprocess.run( + cmd, env=ENV, timeout=TIMEOUTS["version"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True, + ) + return f"{label} {proc.stdout.strip()}" + + +def requested_revisions(): + values = { + name: (os.environ.get(env) or "").strip() + for name, env in REV_ENV.items() + } + set_names = [name for name, value in values.items() if value] + if len(set_names) == 1: + raise UsageError( + f"{REV_ENV[set_names[0]]} is set without the other revision input; " + f"{REV_ENV['diesel-builders']} and {REV_ENV['diesel']} must be provided together" + ) + for name in set_names: + if not REV_RE.fullmatch(values[name]): + raise UsageError( + f"{REV_ENV[name]} must be a full 40-character lowercase git SHA, " + f"got {values[name]!r}" + ) + return values if set_names else {} + + +def source_ref(name, revisions): + if name == "diesel-builders" and revisions: + return f'rev = "{revisions[name]}"' + return 'branch = "main"' + + +def expected_source_prefix(name, revisions): + if name == "diesel-builders" and revisions: + return f"git+{SOURCE_URLS[name]}?rev={revisions[name]}#" + return f"git+{SOURCE_URLS[name]}?branch=main#" + + +def dependency_lines(revisions): + builders_url = SOURCE_URLS["diesel-builders"] + lines = {} + for config, extras in CONFIGS: + line = ( + f'diesel-builders = {{ git = "{builders_url}", ' + f"{source_ref('diesel-builders', revisions)}" + ) + if extras: + line += f", {extras}" + lines[config] = line + " }" + diesel = ( + f'diesel = {{ git = "{SOURCE_URLS["diesel"]}", ' + f"{source_ref('diesel', revisions)}, " + 'features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }' + ) + return lines, diesel + + +def check_metadata(meta, revisions): + by_name = {} + for package in meta["packages"]: + by_name.setdefault(package["name"], []).append(package) + + tuplities = by_name.get("tuplities") or [] + if len(tuplities) != 1: + raise StepFailure( + f"expected exactly one tuplities package in the graph, found {len(tuplities)}", + None, + ) + entry = tuplities[0] + expected = os.path.join(PKG_DIR, "Cargo.toml") + if entry["manifest_path"] != expected or entry.get("source") is not None: + raise StepFailure( + f"tuplities did not resolve to the local package {expected}, " + f"resolved to {entry['manifest_path']} (source={entry.get('source')})", + None, + ) + + resolved = {} + for name in SOURCE_URLS: + entries = by_name.get(name) or [] + if not entries: + raise StepFailure(f"{name} is missing from the resolved graph", None) + prefix = expected_source_prefix(name, revisions) + found = set() + for package in entries: + source = package.get("source") or "" + if not source.startswith(prefix): + raise StepFailure( + f"{name} resolved to the unexpected source {source!r}, expected " + f"{prefix} followed by the revision", + None, + ) + found.add(source.rsplit("#", 1)[-1]) + if len(found) != 1: + raise StepFailure( + f"{name} resolved to more than one revision, " + f"{', '.join(sorted(found))}", + None, + ) + resolved[name] = next(iter(found)) + if revisions and resolved[name] != revisions[name]: + raise StepFailure( + f"{name} resolved to {resolved[name]}, expected {revisions[name]}", + None, + ) + return resolved + + +def parse_markers(output): + return { + line[len("scen "):].strip() + for line in output.splitlines() + if line.startswith("scen ") + } + + +SMOKE_MAIN = r''' +use diesel::prelude::*; +use diesel_builders::prelude::*; +use diesel_builders::{BuilderError, IncompleteBuilderError}; + +/// User model for the `users` table, mirroring the consumer defaults fixture. +#[derive(Debug, Clone, Queryable, Selectable, Identifiable, PartialEq, TableModel)] +#[diesel(table_name = users)] +#[table_model(surrogate_key)] +pub struct User { + pub id: i32, + #[table_model(default = "Guest")] + pub name: String, + #[table_model(default = "User")] + pub role: String, + #[table_model(default = true)] + pub active: bool, + pub bio: Option, + pub email: String, +} + +/// Profile model inheriting from `users` with an inherited ancestor default. +#[derive(Debug, Clone, Queryable, Selectable, Identifiable, PartialEq, TableModel)] +#[diesel(table_name = profiles)] +#[table_model(ancestors(users))] +#[table_model(default(users::bio, "Default bio"))] +pub struct Profile { + pub id: i32, + pub location: String, +} + +fn establish_connection() -> SqliteConnection { + let mut conn = SqliteConnection::establish(":memory:").expect("in-memory SQLite connection"); + diesel::sql_query("PRAGMA foreign_keys = ON") + .execute(&mut conn) + .expect("foreign key pragma"); + conn +} + +fn create_tables(conn: &mut SqliteConnection) -> QueryResult<()> { + use diesel::RunQueryDsl; + diesel::sql_query( + "CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + role TEXT NOT NULL, + active BOOLEAN NOT NULL, + bio TEXT, + email TEXT NOT NULL + )", + ) + .execute(conn)?; + diesel::sql_query( + "CREATE TABLE profiles ( + id INTEGER PRIMARY KEY NOT NULL REFERENCES users(id) ON DELETE CASCADE, + location TEXT NOT NULL + )", + ) + .execute(conn)?; + Ok(()) +} + +fn scenario_defaults() { + let builder = users::table::builder(); + assert_eq!( + builder.may_get_column::(), + Some("Guest".to_string()) + ); + assert_eq!(builder.may_get_column::(), Some("User".to_string())); + assert_eq!(builder.may_get_column::(), Some(true)); + assert_eq!(builder.may_get_column::(), Some(None)); + assert_eq!(builder.may_get_column::(), None); + println!("scen defaults"); +} + +fn scenario_missing_email_error_identity(conn: &mut SqliteConnection) { + let builder = users::table::builder(); + let err = builder.clone().insert(conn).unwrap_err(); + assert!( + matches!( + err, + BuilderError::Incomplete(IncompleteBuilderError::MissingMandatoryField { + table_name: "users", + field_name: "email", + }) + ), + "expected the missing mandatory field identity, got: {err:?}" + ); + println!("scen missing_email_error_identity"); +} + +fn scenario_setters_insert_load(conn: &mut SqliteConnection) -> Result> { + let user = users::table::builder() + .try_name("Admin".to_string())? + .try_bio(Some("Bio".to_string()))? + .try_email("test@example.com".to_string())? + .insert(conn)?; + let loaded = User::find(user.id(), conn)?; + assert_eq!(loaded.id, user.id); + assert_eq!(loaded.name, "Admin"); + assert_eq!(loaded.role, "User"); + assert_eq!(loaded.active, true); + assert_eq!(loaded.bio, Some("Bio".to_string())); + assert_eq!(loaded.email, "test@example.com"); + assert_eq!(loaded, user); + println!("scen setters_insert_load"); + Ok(user) +} + +fn scenario_index_reads(user: &User) { + assert_eq!(*user.id(), user.id); + assert_eq!(user.name(), "Admin"); + assert_eq!(user.role(), "User"); + assert_eq!(user.active(), &true); + assert_eq!(user.bio(), &Some("Bio".to_string())); + println!("scen index_reads"); +} + +fn scenario_sql_query_construction( + conn: &mut SqliteConnection, + user: &User, +) -> Result<(), Box> { + let loaded = <(users::email,) as LoadFirst>::load_first( + ("test@example.com",), + conn, + )?; + assert_eq!(loaded, *user); + let many = <(users::email,) as LoadMany>::load_many( + ("test@example.com",), + conn, + )?; + assert_eq!(many, vec![user.clone()]); + println!("scen sql_query_construction"); + Ok(()) +} + +fn scenario_find_after_mutation( + conn: &mut SqliteConnection, + user: &mut User, +) -> Result<(), Box> { + let id = *user.id(); + user.name = "Changed name".to_string(); + let found = User::find(&id, conn)?; + assert_eq!(found.name, "Admin"); + assert_eq!(user.name, "Changed name"); + println!("scen find_after_mutation"); + Ok(()) +} + +fn scenario_upsert( + conn: &mut SqliteConnection, + user: &mut User, +) -> Result<(), Box> { + user.role = "Moderator".to_string(); + let upserted = user.upsert(conn)?; + assert_eq!(upserted.id, *user.id()); + assert_eq!(upserted.name, "Changed name"); + assert_eq!(upserted.role, "Moderator"); + assert_eq!(upserted.active, true); + assert_eq!(upserted.bio, Some("Bio".to_string())); + assert_eq!(upserted.email, "test@example.com"); + let loaded = User::find(user.id(), conn)?; + assert_eq!(loaded, upserted); + println!("scen upsert"); + Ok(()) +} + +fn scenario_ancestor_bundle( + conn: &mut SqliteConnection, +) -> Result<(Profile, User), Box> { + let builder = profiles::table::builder(); + assert_eq!( + builder.may_get_column::(), + Some(Some("Default bio".to_string())) + ); + assert_eq!( + builder.may_get_column::(), + Some("Guest".to_string()) + ); + assert_eq!(builder.may_get_column::(), None); + + let profile = builder + .try_email("profile@example.com".to_string())? + .location("Rome") + .insert(conn)?; + let profile_user = User::find(profile.id(), conn)?; + assert_eq!(profile_user.name, "Guest"); + assert_eq!(profile_user.role, "User"); + assert_eq!(profile_user.active, true); + assert_eq!(profile_user.bio, Some("Default bio".to_string())); + assert_eq!(profile_user.email, "profile@example.com"); + let loaded = Profile::find(profile.id(), conn)?; + assert_eq!(loaded.location, "Rome"); + assert_eq!(loaded, profile); + println!("scen ancestor_bundle"); + Ok((profile, profile_user)) +} + +fn scenario_ancestor_read( + conn: &mut SqliteConnection, + profile: &Profile, + profile_user: &User, +) -> Result<(), Box> { + let ancestor: User = profile.ancestor(conn)?; + assert_eq!(ancestor, *profile_user); + println!("scen ancestor_read"); + Ok(()) +} + +fn scenario_delete( + conn: &mut SqliteConnection, + profile: &Profile, + user_id: i32, +) -> Result<(), Box> { + let deleted = profile.delete(conn)?; + assert_eq!(deleted, 1); + assert!(!Profile::exists(profile.id(), conn)?); + assert!(!User::exists(profile.id(), conn)?); + assert!(User::exists(&user_id, conn)?); + println!("scen delete"); + Ok(()) +} + +fn main() -> Result<(), Box> { + let mut conn = establish_connection(); + create_tables(&mut conn)?; + + scenario_defaults(); + scenario_missing_email_error_identity(&mut conn); + let mut user = scenario_setters_insert_load(&mut conn)?; + scenario_index_reads(&user); + scenario_sql_query_construction(&mut conn, &user)?; + scenario_find_after_mutation(&mut conn, &mut user)?; + scenario_upsert(&mut conn, &mut user)?; + let (profile, profile_user) = scenario_ancestor_bundle(&mut conn)?; + scenario_ancestor_read(&mut conn, &profile, &profile_user)?; + scenario_delete(&mut conn, &profile, *user.id())?; + + println!("smoke complete"); + Ok(()) +} +''' + +CARGO_TOML = """\ +[package] +name = "diesel-builders-downstream" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +{dep_line} +{diesel_line} + +[workspace] + +[patch."https://github.com/LucaCappelletti94/tuplities"] +tuplities = {{ path = "{pkg_dir}" }} +""" + + +def materialize_project(project_dir, dep_line, diesel_line): + os.makedirs(os.path.join(project_dir, "src"), exist_ok=True) + write_log( + os.path.join(project_dir, "Cargo.toml"), + CARGO_TOML.format( + dep_line=dep_line, + diesel_line=diesel_line, + pkg_dir=PKG_DIR, + ), + ) + write_log(os.path.join(project_dir, "src", "main.rs"), SMOKE_MAIN) + + +def main(): + try: + revisions = requested_revisions() + except UsageError as exc: + print(f"error: {exc}", file=sys.stderr) + sys.exit(2) + mode = "pinned revisions" if revisions else "moving git main" + dep_lines, diesel_line = dependency_lines(revisions) + + rustc_version = run_versioned("rustc", ["rustc", "--version"]) + cargo_version = run_versioned("cargo", [CARGO, "--version"]) + + results = [] + status = "ok" + try: + for config, _extras in CONFIGS: + project_dir = os.path.join(WORK_DIR, config) + config_log_dir = os.path.join(LOG_DIR, config) + os.makedirs(config_log_dir, exist_ok=True) + materialize_project(project_dir, dep_lines[config], diesel_line) + try: + update_args = ["update"] + if revisions: + update_args += ["-p", "diesel", "--precise", revisions["diesel"]] + run_cargo( + config, "update", update_args, project_dir, + os.path.join(config_log_dir, "update.log"), + ) + meta_proc = run_cargo( + config, "metadata", ["metadata", "--format-version", "1"], project_dir, + os.path.join(config_log_dir, "metadata.log"), + ) + resolved = check_metadata(json.loads(meta_proc.stdout), revisions) + run_cargo( + config, "check", ["check", "--all-targets"], project_dir, + os.path.join(config_log_dir, "check.log"), + ) + run_proc = run_cargo( + config, "run", ["run", "--release"], project_dir, + os.path.join(config_log_dir, "run.log"), + ) + seen = parse_markers(run_proc.stdout) + missing = [marker for marker in MARKERS if marker not in seen] + if missing: + raise StepFailure( + f"scenarios omitted from the smoke run: {', '.join(missing)}", + os.path.join(config_log_dir, "run.log"), + ) + results.append((config, "ok", resolved)) + print(f"config {config}: ok") + except StepFailure as exc: + results.append((config, f"failed: {exc}", {})) + print(f"config {config}: FAILED ({exc})", file=sys.stderr) + if exc.log_path: + print(f"logs: {exc.log_path}", file=sys.stderr) + status = "failed" + break + finally: + lines = [ + f"run={RUN_ID} finished={utcnow()} status={status}", + rustc_version, + cargo_version, + f"mode: {mode}", + f"tuplities package: {os.path.join(PKG_DIR, 'Cargo.toml')}", + ] + if revisions: + lines.append( + "requested " + + " ".join(f"{name}={revisions[name]}" for name in SOURCE_URLS) + ) + for config, result, config_revisions in results: + lines.append( + f"config {config}: {result} " + f"diesel-builders={config_revisions.get('diesel-builders', 'unresolved')} " + f"diesel={config_revisions.get('diesel', 'unresolved')}" + ) + write_log(os.path.join(LOG_DIR, "run.log"), "\n".join(lines) + "\n") + + if status != "ok": + sys.exit(1) + print(f"all configurations verified ({mode}), logs under {LOG_DIR}") + + +main() +PY diff --git a/tools/verify_harness_syntax.py b/tools/verify_harness_syntax.py new file mode 100755 index 0000000..7283b01 --- /dev/null +++ b/tools/verify_harness_syntax.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Validate Python files and quoted Python heredocs without executing them.""" + +import ast +import re +import sys +from pathlib import Path + + +HEADER = re.compile( + r"^[^\n]*\bpython(?:\d(?:\.\d+)?)?\b[^\n]*?<<\s*" + r"(?P['\"])(?PPY(?:THON)?)(?P=quote)[^\n]*\n", + re.MULTILINE, +) + + +def check(path): + source = path.read_text() + if path.suffix == ".py": + ast.parse(source, filename=str(path)) + return + for header in HEADER.finditer(source): + if header.group().lstrip().startswith("#"): + continue + closing = re.search( + rf"^{re.escape(header['delimiter'])}[ \t]*$", + source[header.end():], + re.MULTILINE, + ) + if closing is None: + raise ValueError(f"{path} has an unterminated Python heredoc") + body = source[header.end():header.end() + closing.start()] + padding = "\n" * source[:header.end()].count("\n") + ast.parse(padding + body, filename=str(path)) + + +def main(): + paths = [Path(argument) for argument in sys.argv[1:]] + if not paths: + tools = Path(__file__).resolve().parent + paths = sorted([*tools.rglob("*.py"), *tools.rglob("*.sh")]) + for path in paths: + check(path) + print(f"Python syntax verified in {len(paths)} harness files") + + +if __name__ == "__main__": + main() diff --git a/tools/verify_package.sh b/tools/verify_package.sh new file mode 100755 index 0000000..e8af4b5 --- /dev/null +++ b/tools/verify_package.sh @@ -0,0 +1,448 @@ +#!/bin/sh +set -eu + +root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) + +usage() { + echo "usage: verify_package.sh [--allow-dirty]" +} + +allow_dirty=0 +for arg in "$@"; do + case "$arg" in + --allow-dirty) + allow_dirty=1 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "unknown argument: $arg" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [ "$allow_dirty" = 1 ] || [ "${TUPLITIES_ALLOW_DIRTY:-0}" = 1 ]; then + allow_dirty=1 +else + allow_dirty=0 +fi + +exec python3 -B - "$root" "$allow_dirty" <<'PY' +import json +import os +import shlex +import shutil +import signal +import subprocess +import sys +import tempfile +import tarfile +import time +import tomllib +from datetime import datetime, timezone +from pathlib import Path + +ROOT = Path(sys.argv[1]).resolve() +ALLOW_DIRTY = sys.argv[2] == "1" + +PACKAGE_TIMEOUT = 300 +META_TIMEOUT = 120 +TEST_TIMEOUT = 600 +VERSION_TIMEOUT = 30 +WORKLOADS_TIMEOUT = 3000 + +BRIDGE_WIDTHS = [8, 16, 32, 48, 64, 96, 128] +DOCTEST_CASES = ( + ("default", []), + ("no-default", ["--no-default-features"]), +) + + +def resolve(path): + path = Path(path) + return path if path.is_absolute() else (ROOT / path).resolve() + + +PACKAGE_DIR = resolve(os.environ.get("TUPLITIES_PACKAGE_DIR", str(ROOT / "tuplities"))) +OUTPUT_ROOT = resolve( + os.environ.get( + "TUPLITIES_VERIFICATION_DIR", + str(ROOT / "target" / "verification" / "package"), + ) +) +CARGO = shlex.split(os.environ.get("CARGO", "cargo")) +MSRV_TOOLCHAIN = os.environ.get("TUPLITIES_MSRV_TOOLCHAIN", "1.85.0") +MSRV_CARGO = shlex.split(os.environ.get("TUPLITIES_MSRV_CARGO", f"cargo +{MSRV_TOOLCHAIN}")) + + +class StepFailure(Exception): + pass + + +def dependency_requirement(spec): + if isinstance(spec, str): + return spec + return spec.get("version", "*") + + +def execute(cmd, cwd, env, timeout): + command = [str(item) for item in cmd] + with subprocess.Popen(command, cwd=str(cwd), env=env, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, text=True, start_new_session=True) as process: + try: + out, err = process.communicate(timeout=timeout) + except subprocess.TimeoutExpired: + os.killpg(process.pid, signal.SIGKILL) + out, err = process.communicate(timeout=30) + raise subprocess.TimeoutExpired(command, timeout, output=out, stderr=err) from None + return subprocess.CompletedProcess(command, process.returncode, out, err) + + +def run_logged(cmd, cwd, env, log_path, identity, timeout): + started = datetime.now(timezone.utc).isoformat() + header = ( + f"identity={identity}\n" + f"command={shlex.join(map(str, cmd))}\n" + f"started={started}\n" + f"timeout={timeout}s\n" + f"cwd={cwd}\n" + "--- output ---\n" + ) + with open(log_path, "a") as handle: + handle.write(header) + start = time.monotonic() + try: + completed = execute(cmd, cwd, env, timeout) + output = completed.stdout + completed.stderr + exit_code = completed.returncode + stdout = completed.stdout + except subprocess.TimeoutExpired as exc: + stdout = exc.stdout if isinstance(exc.stdout, str) else "" + output = "".join( + part + for part in (exc.stdout or "", exc.stderr or "") + if isinstance(part, str) + ) + exit_code = -1 + duration = time.monotonic() - start + with open(log_path, "a") as handle: + handle.write(output + "--- summary ---\n") + handle.write(f"duration={duration:.2f}s exit={exit_code}\n") + return exit_code, stdout + + +def capture(cmd, cwd, env, timeout): + completed = execute(cmd, cwd, env, timeout) + if completed.returncode != 0: + raise StepFailure(f"command failed: {shlex.join(map(str, cmd))}\n{completed.stderr}") + return completed.stdout + + + +def checkout_facts(): + checkout_manifest = tomllib.loads((PACKAGE_DIR / "Cargo.toml").read_text()) + package = checkout_manifest["package"] + workspace = tomllib.loads((ROOT / "Cargo.toml").read_text()).get("workspace", {}) + workspace_package = workspace.get("package", {}) + + def value(field): + raw = package.get(field) + if isinstance(raw, dict) and raw.get("workspace"): + return workspace_package.get(field) + return raw + + typenum_spec = workspace.get("dependencies", {}).get("typenum") + if typenum_spec is None: + typenum_spec = checkout_manifest.get("dependencies", {}).get("typenum") + if typenum_spec is None: + raise StepFailure("typenum dependency not found in checkout manifest") + facts = { + "name": value("name"), + "version": value("version"), + "rust_version": value("rust-version"), + "typenum_requirement": dependency_requirement(typenum_spec), + } + if facts["name"] != "tuplities": + raise StepFailure(f"package name {facts['name']!r} is not 'tuplities'") + if not facts["version"] or not facts["rust_version"]: + raise StepFailure(f"checkout manifest missing version or rust-version: {facts}") + return facts + + +def inspect_archive(archive, run_dir, facts): + root_prefix = f"{facts['name']}-{facts['version']}" + members = [] + manifest_text = None + with tarfile.open(archive, "r:gz") as archive_file: + for member in sorted(archive_file.getmembers(), key=lambda item: item.name): + members.append(member.name) + if member.isfile() and member.name == f"{root_prefix}/Cargo.toml": + manifest_text = archive_file.extractfile(member).read().decode("utf-8") + (run_dir / "archive-file-list.txt").write_text("\n".join(members) + "\n") + for member in members: + if not member.startswith(root_prefix + "/"): + return f"archive member {member!r} outside root {root_prefix!r}" + parts = Path(member).parts[1:] + if "plans" in parts or any(part.startswith("tuplities-") for part in parts[:-1]): + return f"private plan or satellite package path in archive {member!r}" + manifests = [member for member in members if member.rsplit("/", 1)[-1] == "Cargo.toml"] + if manifests != [f"{root_prefix}/Cargo.toml"]: + return f"archive manifests {manifests} != [{root_prefix}/Cargo.toml]" + locks = [member for member in members if member.rsplit("/", 1)[-1] == "Cargo.lock"] + if locks != [f"{root_prefix}/Cargo.lock"]: + return f"archive locks {locks} != [{root_prefix}/Cargo.lock]" + if manifest_text is None: + return "archive manifest unreadable" + manifest = tomllib.loads(manifest_text) + package = manifest.get("package", {}) + if package.get("name") != facts["name"]: + return f"archive package name {package.get('name')!r} != {facts['name']!r}" + if package.get("version") != facts["version"]: + return f"archive version {package.get('version')!r} != {facts['version']!r}" + if package.get("rust-version") != facts["rust_version"]: + return f"archive rust-version {package.get('rust-version')!r} != {facts['rust_version']!r}" + dependencies = manifest.get("dependencies", {}) + if set(dependencies) != {"typenum"}: + return f"archive dependencies {sorted(dependencies)} != ['typenum']" + typenum_spec = dependencies["typenum"] + if isinstance(typenum_spec, dict) and typenum_spec.get("optional"): + return "typenum dependency is optional" + requirement = dependency_requirement(typenum_spec) + if requirement != facts["typenum_requirement"]: + return f"archive typenum requirement {requirement!r} != {facts['typenum_requirement']!r}" + normalized = { + "name": package["name"], + "version": package["version"], + "rust_version": package["rust-version"], + "dependencies": {name: dependency_requirement(spec) for name, spec in dependencies.items()}, + } + (run_dir / "archive-manifest.json").write_text(json.dumps(normalized, indent=2) + "\n") + (run_dir / "manifests").mkdir() + (run_dir / "manifests" / "Cargo.toml").write_text(manifest_text) + return None + + +def extract_archive(archive, extract_root, facts): + root_prefix = f"{facts['name']}-{facts['version']}" + with tarfile.open(archive, "r:gz") as archive_file: + archive_file.extractall(extract_root, filter="data") + entries = sorted(entry.name for entry in extract_root.iterdir()) + if entries != [root_prefix]: + return f"extracted entries {entries} != [{root_prefix}]" + return None + + +def check_metadata(meta, facts): + packages = {package["name"]: package for package in meta["packages"]} + for name in packages: + if name != facts["name"] and name.startswith("tuplities-"): + return f"satellite package in metadata: {name}" + by_id = {package["id"]: package for package in meta["packages"]} + members = [by_id[member_id]["name"] for member_id in meta["workspace_members"]] + if members != [facts["name"]]: + return f"workspace members {members} != [{facts['name']}]" + if set(packages) != {facts["name"], "typenum"}: + return f"metadata packages {sorted(packages)} != [{facts['name']}, 'typenum']" + package = packages[facts["name"]] + if package["version"] != facts["version"]: + return f"package version {package['version']!r} != {facts['version']!r}" + if package.get("rust_version") != facts["rust_version"]: + return f"package rust_version {package.get('rust_version')!r} != {facts['rust_version']!r}" + production = [dep for dep in package["dependencies"] if dep.get("kind") in (None, "normal")] + if [dep["name"] for dep in production] != ["typenum"]: + return f"production dependencies {sorted(dep['name'] for dep in production)} != ['typenum']" + if production[0].get("optional"): + return "typenum dependency is optional" + return None + + +def verify_metadata(text, facts, run_dir): + try: + meta = json.loads(text) + except (ValueError, IndexError) as exc: + return f"metadata output unparseable: {exc}" + error = check_metadata(meta, facts) + if error: + return error + (run_dir / "metadata.json").write_text(json.dumps(meta, indent=2) + "\n") + return None + + + +def main(): + run_dir = OUTPUT_ROOT / (datetime.now(timezone.utc).strftime("run-%Y%m%d-%H%M%S-pid") + str(os.getpid())) + run_dir.mkdir(parents=True) + temp_base = Path(tempfile.mkdtemp(prefix="tuplities-package-verify-")) + extract_root = temp_base / "source" + extract_root.mkdir() + steps = [] + facts = None + failure = None + archive_path = None + + def record(name, status, duration, log=None): + entry = {"step": name, "status": status, "duration": round(duration, 2)} + if log is not None: + entry["log"] = str(log) + steps.append(entry) + + def run_step(name, cmd, cwd, env, log, timeout, verify=None): + identity = f"{run_dir.name}/{name}" + start = time.monotonic() + error = None + try: + exit_code, stdout = run_logged(cmd, cwd, env, log, identity, timeout) + if exit_code != 0: + error = f"exit={exit_code}" + elif verify is not None: + error = verify(stdout) + except Exception as exc: + error = f"{type(exc).__name__} {exc}" + duration = time.monotonic() - start + record(name, "ok" if error is None else "failed", duration, log) + if error is not None: + raise StepFailure(f"{name}: {error}, log={log}") + + def process_step(name, func): + start = time.monotonic() + try: + result = func() + except Exception as exc: + record(name, "failed", time.monotonic() - start) + raise StepFailure(f"{name} failed ({type(exc).__name__}) {exc}") from exc + record(name, "ok", time.monotonic() - start) + return result + + try: + facts = process_step("checkout-manifest", checkout_facts) + name = facts["name"] + version = facts["version"] + + def versions(): + lines = [] + for label, command in ( + ("cargo", CARGO + ["--version"]), + ("msrv-cargo", MSRV_CARGO + ["--version"]), + ): + lines.append(f"{label}={capture(command, ROOT, os.environ, VERSION_TIMEOUT).strip()}") + rustc = ["rustc", "--version"] + if len(MSRV_CARGO) > 1 and MSRV_CARGO[1].startswith("+"): + rustc = ["rustup", "run", MSRV_CARGO[1][1:], *rustc] + lines.append(f"msrv-rustc={capture(rustc, ROOT, os.environ, VERSION_TIMEOUT).strip()}") + (run_dir / "versions.txt").write_text("\n".join(lines) + "\n") + + process_step("versions", versions) + + package_cmd = ["nice", "-n", "10", *CARGO, "package", "-p", name, "--locked", "-j", "4"] + if ALLOW_DIRTY: + package_cmd.append("--allow-dirty") + run_step("package", package_cmd, ROOT, os.environ, run_dir / "package.log", PACKAGE_TIMEOUT) + archive = ROOT / "target" / "package" / f"{name}-{version}.crate" + if not archive.is_file(): + raise StepFailure(f"expected archive missing: {archive}") + archive_path = archive + + def archive_inspect(): + error = inspect_archive(archive, run_dir, facts) + if error: + raise StepFailure(error) + + process_step("archive-inspect", archive_inspect) + + def extract(): + error = extract_archive(archive, extract_root, facts) + if error: + raise StepFailure(error) + + process_step("extract", extract) + extracted = extract_root / f"{name}-{version}" + + run_step( + "metadata", + ["nice", "-n", "10", *MSRV_CARGO, "metadata", "--format-version", "1", "--locked"], + extracted, + os.environ, + run_dir / "metadata.log", + META_TIMEOUT, + verify=lambda text: verify_metadata(text, facts, run_dir), + ) + + for case_name, extra in DOCTEST_CASES: + doc_env = {**os.environ, "CARGO_TARGET_DIR": str(run_dir / "target" / f"doc-{case_name}")} + run_step( + f"doctest-{case_name}", + ["nice", "-n", "10", *MSRV_CARGO, "test", "-p", name, "--doc", "--locked", "-j", "4", *extra], + extracted, + doc_env, + run_dir / f"doctest-{case_name}.log", + TEST_TIMEOUT, + ) + + for width in BRIDGE_WIDTHS: + features = "flatten-nest" if width == 8 else f"flatten-nest,size-{width}" + bridge_env = { + **os.environ, + "CARGO_TARGET_DIR": str(run_dir / "target" / f"bridge-{width}"), + } + run_step( + f"bridge-{width}", + [ + "nice", "-n", "10", *MSRV_CARGO, "test", "-p", name, + "--no-default-features", f"--features={features}", + "--test", "flat_bridge_contracts", + "--test", "flat_bridge_sizes", + "--locked", "-j", "4", + ], + extracted, + bridge_env, + run_dir / f"bridge-{width}.log", + TEST_TIMEOUT, + ) + + harness_env = { + **os.environ, + "CARGO": shlex.join(MSRV_CARGO), + "TUPLITIES_PACKAGE_DIR": str(extracted), + "TUPLITIES_VERIFICATION_DIR": str(run_dir / "compile-workloads"), + } + run_step( + "compile-workloads", + [str(ROOT / "tools" / "verify_compile_workloads.sh")], + ROOT, + harness_env, + run_dir / "compile-workloads.log", + WORKLOADS_TIMEOUT, + ) + except StepFailure as step_failure: + failure = str(step_failure) + finally: + shutil.rmtree(temp_base) + summary = { + "run_dir": str(run_dir), + "package": (facts or {}).get("name"), + "version": (facts or {}).get("version"), + "msrv_toolchain": MSRV_TOOLCHAIN, + "msrv_cargo": shlex.join(MSRV_CARGO), + "allow_dirty": ALLOW_DIRTY, + "archive": str(archive_path) if archive_path else None, + "steps": steps, + "failure": failure, + } + (run_dir / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") + + print(f"run dir: {run_dir}") + if archive_path is not None: + print(f"archive: {archive_path}") + if failure is not None: + print(f"FAILED {failure}", file=sys.stderr) + return 1 + print("ok") + return 0 + + +sys.exit(main()) +PY diff --git a/tuplities-clone/Cargo.toml b/tuplities-clone/Cargo.toml deleted file mode 100644 index 7437739..0000000 --- a/tuplities-clone/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-clone" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for cloning tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-clone/src/lib.rs b/tuplities-clone/src/lib.rs deleted file mode 100644 index 4307996..0000000 --- a/tuplities-clone/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleClone` trait. - -#[tuplities_derive::impl_tuple_clone] -/// A trait for cloning tuples. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleClone { - #[must_use] - /// Clones `self` into a new instance. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_clone::TupleClone; - /// - /// let tuple = (1, "hello", vec![1, 2, 3]); - /// let cloned_tuple = tuple.tuple_clone(); - /// - /// assert_eq!(tuple, cloned_tuple); - /// ``` - fn tuple_clone(&self) -> Self; -} diff --git a/tuplities-copy/Cargo.toml b/tuplities-copy/Cargo.toml deleted file mode 100644 index 29e9595..0000000 --- a/tuplities-copy/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "tuplities-copy" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for copying tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true -tuplities-clone.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-copy/src/lib.rs b/tuplities-copy/src/lib.rs deleted file mode 100644 index 171459d..0000000 --- a/tuplities-copy/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleCopy` trait. - -use tuplities_clone::TupleClone; - -#[tuplities_derive::impl_tuple_copy] -/// A trait for copying tuples. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleCopy: TupleClone { - #[must_use] - /// Copies `self` into a new instance. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_copy::TupleCopy; - /// - /// let tuple = (1, "hello", 3.14); - /// let copied_tuple = tuple.tuple_copy(); - /// - /// assert_eq!(tuple, copied_tuple); - /// ``` - fn tuple_copy(&self) -> Self; -} diff --git a/tuplities-debug/Cargo.toml b/tuplities-debug/Cargo.toml deleted file mode 100644 index e771047..0000000 --- a/tuplities-debug/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-debug" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for debug formatting of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-debug/src/lib.rs b/tuplities-debug/src/lib.rs deleted file mode 100644 index ecca183..0000000 --- a/tuplities-debug/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleDebug` trait. -#![no_std] - -extern crate alloc; - -#[tuplities_derive::impl_tuple_debug] -/// A trait for debugging tuples. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleDebug { - /// Returns a string representation of the tuple for debugging. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_debug::TupleDebug; - /// - /// let tuple = (1, "hello", 3.14); - /// let debug_str = tuple.tuple_debug(); - /// - /// assert_eq!(debug_str, "(1, \"hello\", 3.14)"); - /// ``` - fn tuple_debug(&self) -> alloc::string::String; -} diff --git a/tuplities-default/Cargo.toml b/tuplities-default/Cargo.toml deleted file mode 100644 index 6d884b9..0000000 --- a/tuplities-default/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-default" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for creating default values for tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-default/src/lib.rs b/tuplities-default/src/lib.rs deleted file mode 100644 index 051b127..0000000 --- a/tuplities-default/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleDefault` trait. - -#[tuplities_derive::impl_tuple_default] -/// A trait for creating default instances of tuples. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleDefault { - /// Returns the default value of the tuple. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_default::TupleDefault; - /// - /// let default_tuple: (i32, String, f64) = <(i32, String, f64)>::tuple_default(); - /// - /// assert_eq!(default_tuple, (0, String::new(), 0.0)); - /// ``` - fn tuple_default() -> Self; -} diff --git a/tuplities-derive/Cargo.toml b/tuplities-derive/Cargo.toml deleted file mode 100644 index c8dff5f..0000000 --- a/tuplities-derive/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "tuplities-derive" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "Procedural macros for deriving tuple traits" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[lib] -proc-macro = true - -[dependencies] -syn = { version = "2.0", features = ["full", "extra-traits"] } -quote = "1.0" -proc-macro2 = "1.0" -siphasher = "1.0" -typenum = { workspace = true } - -[features] -default = [] -size-16 = [] -size-32 = [] -size-48 = [] -size-64 = [] -size-96 = [] -size-128 = [] - -[lints] -workspace = true diff --git a/tuplities-derive/src/lib.rs b/tuplities-derive/src/lib.rs deleted file mode 100644 index 77d3459..0000000 --- a/tuplities-derive/src/lib.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! Procedural macros for the tuplities suite. - -use proc_macro::TokenStream; - -mod tuple_size; -mod tuplities_as_ref; -mod tuplities_clone; -mod tuplities_copy; -mod tuplities_debug; -mod tuplities_default; -mod tuplities_eq; -mod tuplities_first_tuple_row; -mod tuplities_from; -mod tuplities_hash; -mod tuplities_index; -mod tuplities_insert; -mod tuplities_last_tuple_row; -mod tuplities_len; -mod tuplities_mut; -mod tuplities_mut_map; -mod tuplities_nest_tuple; -mod tuplities_option; -mod tuplities_ord; -mod tuplities_partial_eq; -mod tuplities_partial_ord; -mod tuplities_pop_back; -mod tuplities_pop_front; -mod tuplities_push_back; -mod tuplities_push_front; -mod tuplities_ref; -mod tuplities_ref_map; -mod tuplities_remove; -mod tuplities_replicate; -mod tuplities_reverse; -mod tuplities_row; -mod tuplities_row_mut; -mod tuplities_split; -mod tuplities_try_from; - -/// Generate `TableIndex` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_clone(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_clone::impl_tuple_clone()); - item.into() -} - -/// Generate `TupleCopy` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_copy(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_copy::impl_tuple_copy()); - item.into() -} - -/// Generate `TupleDebug` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_debug(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_debug::impl_tuple_debug()); - item.into() -} - -/// Generate `TupleDefault` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_default(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_default::impl_tuple_default()); - item.into() -} - -/// Generate `TupleHash` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_hash(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_hash::impl_tuple_hash()); - item.into() -} - -/// Generate `TupleAsRef` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_as_ref(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_as_ref::impl_tuple_as_ref()); - item.into() -} - -/// Generate `TuplePartialEq` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_partial_eq(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_partial_eq::impl_tuple_partial_eq()); - item.into() -} - -/// Generate `TupleEq` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_eq(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_eq::impl_tuple_eq()); - item.into() -} - -/// Generate `TuplePartialOrd` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_partial_ord(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_partial_ord::impl_tuple_partial_ord()); - item.into() -} - -/// Generate `TupleOrd` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_ord(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_ord::impl_tuple_ord()); - item.into() -} - -/// Generate `PopFront` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_pop_front(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_pop_front::impl_pop_front()); - item.into() -} - -/// Generate `PopBack` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_pop_back(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_pop_back::impl_pop_back()); - item.into() -} - -/// Generate `TupleRemove` trait implementations for all tuple sizes and indices. -#[proc_macro_attribute] -pub fn impl_remove(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_remove::impl_remove()); - item.into() -} - -/// Generate `TupleInsert` trait implementations for all tuple sizes and indices. -#[proc_macro_attribute] -pub fn impl_insert(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_insert::impl_insert()); - item.into() -} - -/// Generate `TupleLen` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_len(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_len::impl_len()); - item.into() -} - -/// Generate `TupleIndex` trait implementations for all tuple sizes and indices. -#[proc_macro_attribute] -pub fn impl_tuple_index(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_index::impl_tuple_index()); - item.into() -} - -/// Generate `PushBack` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_push_back(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_push_back::impl_push_back()); - item.into() -} - -/// Generate `PushFront` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_push_front(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_push_front::impl_push_front()); - item.into() -} - -/// Generate `TupleReverse` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_reverse(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_reverse::impl_reverse()); - item.into() -} - -/// Generate `TupleSplit` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_split(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_split::impl_split()); - item.into() -} - -/// Generate `TupleRef` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_ref(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_ref::impl_tuple_ref()); - item.into() -} - -/// Generate `TupleReplicate` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_replicate(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_replicate::impl_tuple_replicate()); - item.into() -} - -/// Generate `TupleMut` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_mut(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_mut::impl_tuple_mut()); - item.into() -} - -/// Generate `TupleRefMap` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_ref_map(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_ref_map::impl_tuple_ref_map()); - item.into() -} - -/// Generate `TupleMutMap` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_mut_map(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_mut_map::impl_tuple_mut_map()); - item.into() -} - -/// Generate `TupleOption` and `IntoTupleOption` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_option(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_option::impl_tuple_option()); - item.extend(tuplities_option::impl_into_tuple_option()); - item.into() -} - -/// Generate `TupleTryFrom` and `TupleTryInto` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_try_from(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_try_from::impl_tuple_try_from()); - item.into() -} - -/// Generate `TupleFrom` and `TupleInto` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_tuple_from(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_from::impl_tuple_from()); - item.into() -} - -/// Generate `Row` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_row(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_row::impl_row()); - item.into() -} - -/// Generate `RowMut` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_row_mut(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_row_mut::impl_row_mut()); - item.into() -} - -/// Generate `FirstTupleRow` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_first_tuple_row(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_first_tuple_row::impl_first_tuple_row()); - item.into() -} - -/// Generate `LastTupleRow` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_last_tuple_row(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_last_tuple_row::impl_last_tuple_row()); - item.into() -} - -/// Generate `NestTuple` trait implementations for all tuple sizes. -#[proc_macro_attribute] -pub fn impl_nest_tuple(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut item = proc_macro2::TokenStream::from(item); - item.extend(tuplities_nest_tuple::impl_nest_tuple()); - item.into() -} diff --git a/tuplities-derive/src/tuple_size.rs b/tuplities-derive/src/tuple_size.rs deleted file mode 100644 index 15ff9a8..0000000 --- a/tuplities-derive/src/tuple_size.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! Core tuple generation utilities for procedural macros. -//! -//! This module provides shared functionality for generating trait -//! implementations across tuples of varying sizes (1-32 elements). - -use proc_macro2::{Ident, Span, TokenStream}; -use syn::Index; - -#[cfg(not(any( - feature = "size-16", - feature = "size-32", - feature = "size-48", - feature = "size-64", - feature = "size-96", - feature = "size-128" -)))] -/// Maximum number of elements supported in tuple implementations. -pub const MAX_TUPLE_SIZE: usize = 8; -#[cfg(all( - feature = "size-16", - not(any( - feature = "size-32", - feature = "size-48", - feature = "size-64", - feature = "size-96", - feature = "size-128" - )) -))] -/// Maximum number of elements supported in tuple implementations. -pub const MAX_TUPLE_SIZE: usize = 16; -#[cfg(all( - feature = "size-32", - not(any( - feature = "size-48", - feature = "size-64", - feature = "size-96", - feature = "size-128" - )) -))] -/// Maximum number of elements supported in tuple implementations. -pub const MAX_TUPLE_SIZE: usize = 32; -#[cfg(all( - feature = "size-48", - not(any(feature = "size-64", feature = "size-96", feature = "size-128")) -))] -/// Maximum number of elements supported in tuple implementations. -pub const MAX_TUPLE_SIZE: usize = 48; -#[cfg(all( - feature = "size-64", - not(feature = "size-96"), - not(feature = "size-128") -))] -/// Maximum number of elements supported in tuple implementations. -pub const MAX_TUPLE_SIZE: usize = 64; -#[cfg(all(feature = "size-96", not(feature = "size-128")))] -/// Maximum number of elements supported in tuple implementations. -pub const MAX_TUPLE_SIZE: usize = 96; -#[cfg(feature = "size-128")] -/// Maximum number of elements supported in tuple implementations. -pub const MAX_TUPLE_SIZE: usize = 128; - -/// Generate a list of type parameter identifiers (T1, T2, ..., TN) -/// -/// # Arguments -/// -/// * `count` - The number of type parameters to generate. -pub(crate) fn type_params(count: usize) -> Vec { - type_params_with_prefix(count, "T") -} - -pub(crate) fn type_params_with_prefix(count: usize, prefix: &str) -> Vec { - (1..=count) - .map(|i| Ident::new(&format!("{prefix}{i}"), Span::call_site())) - .collect() -} - -/// Generate a list of indices (0, 1, ..., N-1) -pub(crate) fn indices(count: usize) -> Vec { - (0..count).map(Index::from).collect() -} - -/// Generate all tuple implementations from size 0 (unit) to MAX_TUPLE_SIZE -pub(crate) fn generate_all_sizes(impl_fn: F) -> TokenStream -where - F: Fn(usize) -> TokenStream, -{ - (0..=MAX_TUPLE_SIZE).map(impl_fn).collect() -} - -/// Generate all tuple implementations from size 0 (unit) to MAX_TUPLE_SIZE -pub(crate) fn generate_non_empty(impl_fn: F) -> TokenStream -where - F: Fn(usize) -> TokenStream, -{ - (1..=MAX_TUPLE_SIZE).map(impl_fn).collect() -} - -mod tests { - #[test] - fn test_type_params() { - let params = super::type_params(3); - assert_eq!(params.len(), 3); - assert_eq!(params[0].to_string(), "T1"); - assert_eq!(params[1].to_string(), "T2"); - assert_eq!(params[2].to_string(), "T3"); - } - - #[test] - fn test_generate_all_sizes() { - let _generated = super::generate_all_sizes(|size| { - let struct_ident = - syn::Ident::new(&format!("TupleSize{size}"), proc_macro2::Span::call_site()); - quote::quote! { - struct #struct_ident; - } - }); - } -} diff --git a/tuplities-derive/src/tuplities_as_ref.rs b/tuplities-derive/src/tuplities_as_ref.rs deleted file mode 100644 index f3ff195..0000000 --- a/tuplities-derive/src/tuplities_as_ref.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Submodule providing the derive macro for the `TupleAsRef` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params, type_params_with_prefix}; - -/// Generate `TupleAsRef` trait implementations for all tuple sizes. -pub fn impl_tuple_as_ref() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let ref_target_params = type_params_with_prefix(size, "R"); - let indices = indices(size); - - quote! { - impl<'a, #(#ref_target_params: 'a + ?Sized,)* #(#type_params: AsRef<#ref_target_params>,)*> TupleAsRef<'a, (#(&'a #ref_target_params,)*)> for (#(#type_params,)*) - where Self: 'a - { - #[inline] - fn as_tuple_ref(&'a self) -> (#(&'a #ref_target_params,)*) { - (#(self.#indices.as_ref(),)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_clone.rs b/tuplities-derive/src/tuplities_clone.rs deleted file mode 100644 index 3335af5..0000000 --- a/tuplities-derive/src/tuplities_clone.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Submodule providing the derive macro for the `TupleClone` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TableIndex` trait marker implementations for all tuple sizes. -pub fn impl_tuple_clone() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: Clone,)*> TupleClone for (#(#type_params,)*) - { - #[inline] - - fn tuple_clone(&self) -> Self { - (#(self.#indices.clone(),)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_copy.rs b/tuplities-derive/src/tuplities_copy.rs deleted file mode 100644 index e59eff7..0000000 --- a/tuplities-derive/src/tuplities_copy.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Submodule providing the derive macro for the `TupleCopy` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleCopy` trait implementations for all tuple sizes. -pub fn impl_tuple_copy() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: Copy,)*> TupleCopy for (#(#type_params,)*) - { - #[inline] - fn tuple_copy(&self) -> Self { - ( #(self.#indices,)* ) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_debug.rs b/tuplities-derive/src/tuplities_debug.rs deleted file mode 100644 index 00dd5ba..0000000 --- a/tuplities-derive/src/tuplities_debug.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Submodule providing the derive macro for the `TupleDebug` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleDebug` trait implementations for all tuple sizes. -pub fn impl_tuple_debug() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: core::fmt::Debug,)*> TupleDebug for (#(#type_params,)*) - { - #[inline] - fn tuple_debug(&self) -> alloc::string::String { - let parts: alloc::vec::Vec = alloc::vec![ - #( - alloc::format!("{:?}", self.#indices), - )* - ]; - alloc::format!( - "({})", - parts.join(", ") - ) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_default.rs b/tuplities-derive/src/tuplities_default.rs deleted file mode 100644 index 5c6ad66..0000000 --- a/tuplities-derive/src/tuplities_default.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Submodule providing the derive macro for the `TupleDefault` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, type_params}; - -/// Generate `TupleDefault` trait implementations for all tuple sizes. -pub fn impl_tuple_default() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - - quote! { - impl<#(#type_params: Default,)*> TupleDefault for (#(#type_params,)*) - { - #[inline] - fn tuple_default() -> Self { - ( #(<#type_params as Default>::default(),)* ) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_eq.rs b/tuplities-derive/src/tuplities_eq.rs deleted file mode 100644 index 10949e9..0000000 --- a/tuplities-derive/src/tuplities_eq.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Submodule providing the derive macro for the `TupleEq` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleEq` trait implementations for all tuple sizes. -pub fn impl_tuple_eq() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - let body = if size == 0 { - quote! { true } - } else { - quote! { #(self.#indices == other.#indices)&&* } - }; - - quote! { - impl<#(#type_params: Eq,)*> TupleEq for (#(#type_params,)*) - { - #[inline] - fn tuple_eq(&self, other: &Self) -> bool { - #body - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_first_tuple_row.rs b/tuplities-derive/src/tuplities_first_tuple_row.rs deleted file mode 100644 index f275020..0000000 --- a/tuplities-derive/src/tuplities_first_tuple_row.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Submodule providing the derive macro for the `FirstTupleRow` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `FirstTupleRow` trait implementations for all tuple sizes. -pub fn impl_first_tuple_row() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: tuplities_pop_front::TupleRefFront,)*> FirstTupleRow for (#(#type_params,)*) - { - type FirstRowType = (#(< #type_params as tuplities_pop_front::TuplePopFront >::Front,)*); - - fn first_tuple_row(&self) -> ::Ref<'_> { - (#(self.#indices.ref_front(),)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_from.rs b/tuplities-derive/src/tuplities_from.rs deleted file mode 100644 index cde0297..0000000 --- a/tuplities-derive/src/tuplities_from.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Submodule providing the derive macro for the `TupleFrom` and `TupleInto` traits. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params, type_params_with_prefix}; - -/// Generate `TupleFrom` trait implementations for all tuple sizes. -pub fn impl_tuple_from() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let source_type_params = type_params(size); - let target_type_params = type_params_with_prefix(size, "B"); - let indices = indices(size); - - if size == 0 { - return quote! { - impl TupleFrom<()> for () { - fn tuple_from(_value: ()) -> Self {} - } - }; - } - - let source_tuple = quote! { (#(#source_type_params,)*) }; - - quote! { - impl<#(#source_type_params,)* #(#target_type_params: From<#source_type_params>,)*> TupleFrom<#source_tuple> for (#(#target_type_params,)*) - { - fn tuple_from(value: #source_tuple) -> Self { - (#(From::from(value.#indices),)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_hash.rs b/tuplities-derive/src/tuplities_hash.rs deleted file mode 100644 index 2db9fc5..0000000 --- a/tuplities-derive/src/tuplities_hash.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Submodule providing the derive macro for the `TupleHash` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleHash` trait implementations for all tuple sizes. -pub fn impl_tuple_hash() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: core::hash::Hash + Eq,)*> TupleHash for (#(#type_params,)*) - { - #[inline] - fn tuple_hash(&self, state: &mut H) { - #( - self.#indices.hash(state); - )* - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_index.rs b/tuplities-derive/src/tuplities_index.rs deleted file mode 100644 index 95f3362..0000000 --- a/tuplities-derive/src/tuplities_index.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Submodule providing the derive macro for the `TupleIndex` trait. - -use quote::quote; - -use crate::tuple_size::{generate_non_empty, type_params}; - -/// Generate `TupleIndex` trait implementations for all tuple sizes and indices. -pub fn impl_tuple_index() -> proc_macro2::TokenStream { - generate_non_empty(|size| { - let type_params = type_params(size); - (0..size) - .map(|u_idx| { - let indexed_type = &type_params[u_idx]; - let indexed_index = syn::Index::from(u_idx); - let typenum_ident = - syn::Ident::new(&format!("U{u_idx}"), proc_macro2::Span::call_site()); - let typenum: syn::Path = syn::parse_quote!(typenum::#typenum_ident); - quote! { - impl<#(#type_params,)*> TupleIndex<#typenum> for (#(#type_params,)*) - { - type Element = #indexed_type; - - #[inline] - fn tuple_index(&self) -> &Self::Element { - &self.#indexed_index - } - } - impl<#(#type_params,)*> TupleIndexMut<#typenum> for (#(#type_params,)*) - { - #[inline] - fn tuple_index_mut(&mut self) -> &mut Self::Element { - &mut self.#indexed_index - } - } - } - }) - .collect() - }) -} diff --git a/tuplities-derive/src/tuplities_insert.rs b/tuplities-derive/src/tuplities_insert.rs deleted file mode 100644 index f38da2d..0000000 --- a/tuplities-derive/src/tuplities_insert.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Submodule providing the derive macro for the `TupleInsert` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleInsert` trait implementations for all tuple sizes and indices. -pub fn impl_insert() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - (0..=size) - .map(|u_idx| { - let before = &type_params[..u_idx]; - let after = &type_params[u_idx..]; - let before_indices = &indices[..u_idx]; - let after_indices = &indices[u_idx..]; - let typenum_ident = - syn::Ident::new(&format!("U{u_idx}"), proc_macro2::Span::call_site()); - let typenum: syn::Path = syn::parse_quote!(typenum::#typenum_ident); - quote! { - impl TupleInsert<#typenum, T> for (#(#type_params,)*) - { - type Output = (#(#before,)* T, #(#after,)*); - - #[inline] - fn insert(self, value: T) -> Self::Output { - (#(self.#before_indices,)* value, #(self.#after_indices,)*) - } - } - } - }) - .collect() - }) -} diff --git a/tuplities-derive/src/tuplities_last_tuple_row.rs b/tuplities-derive/src/tuplities_last_tuple_row.rs deleted file mode 100644 index 5a70e57..0000000 --- a/tuplities-derive/src/tuplities_last_tuple_row.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Submodule providing the derive macro for the `LastTupleRow` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `LastTupleRow` trait implementations for all tuple sizes. -pub fn impl_last_tuple_row() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: tuplities_pop_back::TupleRefBack,)*> LastTupleRow for (#(#type_params,)*) - { - type LastRowType = (#(< #type_params as tuplities_pop_back::TuplePopBack >::Back,)*); - - fn last_tuple_row(&self) -> ::Ref<'_> { - (#(self.#indices.ref_back(),)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_len.rs b/tuplities-derive/src/tuplities_len.rs deleted file mode 100644 index 3c91f0e..0000000 --- a/tuplities-derive/src/tuplities_len.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Submodule providing the derive macro for the `TupleLen` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, type_params}; - -/// Generate `TupleLen` trait implementations for all tuple sizes. -pub fn impl_len() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let typenum_ident = syn::Ident::new(&format!("U{size}"), proc_macro2::Span::call_site()); - let typenum: syn::Path = syn::parse_quote!(typenum::#typenum_ident); - quote! { - impl<#(#type_params,)*> TupleLen for (#(#type_params,)*) { - type Len = #typenum; - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_mut.rs b/tuplities-derive/src/tuplities_mut.rs deleted file mode 100644 index 5c02246..0000000 --- a/tuplities-derive/src/tuplities_mut.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Submodule providing the derive macro for the `TupleMut` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleMut` trait implementations for all tuple sizes. -pub fn impl_tuple_mut() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - let mut_tuple = if size == 0 { - quote! { () } - } else { - let mut_refs = (0..size).map(|_| quote! { &'a mut }); - quote! { (#(#mut_refs #type_params,)*) } - }; - - quote! { - impl<#(#type_params,)*> TupleMut for (#(#type_params,)*) - { - type Mut<'a> = #mut_tuple where Self: 'a; - - #[inline] - fn tuple_mut(&mut self) -> Self::Mut<'_> { - (#(&mut self.#indices,)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_mut_map.rs b/tuplities-derive/src/tuplities_mut_map.rs deleted file mode 100644 index 68a9271..0000000 --- a/tuplities-derive/src/tuplities_mut_map.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Submodule providing the derive macro for the `TupleMutMap` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleMutMap` trait implementations for all tuple sizes. -pub fn impl_tuple_mut_map() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: TupleMut,)*> TupleMutMap for (#(#type_params,)*) - { - type MutMap<'a> = (#(< #type_params as TupleMut >::Mut<'a>,)*) where Self: 'a; - - #[inline] - fn tuple_mut_map(&mut self) -> Self::MutMap<'_> { - (#(self.#indices.tuple_mut(),)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_nest_tuple.rs b/tuplities-derive/src/tuplities_nest_tuple.rs deleted file mode 100644 index 6d542d4..0000000 --- a/tuplities-derive/src/tuplities_nest_tuple.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! Submodule providing the derive macro for the `NestTuple` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, type_params}; - -/// Generate `NestTuple` trait implementations for all tuple sizes. -pub fn impl_nest_tuple() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - - // Generate the flat input type - let flat_input_type = quote! { (#(#type_params,)*) }; - - // Generate the nested output type - let nested_output_type = generate_nested_output_type(&type_params); - let nested_ref_output_type = generate_nested_ref_output_type(&type_params); - let nested_mut_output_type = generate_nested_mut_output_type(&type_params); - - // Generate the nest implementation - let nest_impl = generate_nest_impl(size); - let nest_ref_impl = generate_nest_ref_impl(size); - let nest_mut_impl = generate_nest_mut_impl(size); - - quote! { - impl<#(#type_params,)*> NestTuple for #flat_input_type { - type Nested = #nested_output_type; - - #[inline] - fn nest(self) -> Self::Nested { - #nest_impl - } - } - - impl<#(#type_params,)*> NestTupleRef for #flat_input_type { - type NestedRef<'a> = #nested_ref_output_type where Self: 'a; - - #[inline] - fn nest_ref(&self) -> Self::NestedRef<'_> { - #nest_ref_impl - } - } - - impl<#(#type_params,)*> NestTupleMut for #flat_input_type { - type NestedMut<'a> = #nested_mut_output_type where Self: 'a; - - #[inline] - fn nest_mut(&mut self) -> Self::NestedMut<'_> { - #nest_mut_impl - } - } - } - }) -} - -/// Generate the nested output type for NestTuple. -/// For size N with types A, B, C, ..., N, generates (A, (B, (C, (...(N,))))) -fn generate_nested_output_type(type_params: &[proc_macro2::Ident]) -> proc_macro2::TokenStream { - match type_params { - [] => quote! { () }, - [single] => quote! { (#single,) }, - _ => { - // Build nested structure: (T0, (T1, (T2, (...(T{N-1},)))) - // Start with innermost (T{N-1},) and wrap outwards - let last_ty = &type_params[type_params.len() - 1]; - let mut result = quote! { (#last_ty,) }; - for ty in type_params.iter().rev().skip(1) { - result = quote! { (#ty, #result) }; - } - result - } - } -} - -fn generate_nested_ref_output_type(type_params: &[proc_macro2::Ident]) -> proc_macro2::TokenStream { - match type_params { - [] => quote! { () }, - [single] => quote! { (&'a #single,) }, - _ => { - let last_ty = &type_params[type_params.len() - 1]; - let mut result = quote! { (&'a #last_ty,) }; - for ty in type_params.iter().rev().skip(1) { - result = quote! { (&'a #ty, #result) }; - } - result - } - } -} - -fn generate_nested_mut_output_type(type_params: &[proc_macro2::Ident]) -> proc_macro2::TokenStream { - match type_params { - [] => quote! { () }, - [single] => quote! { (&'a mut #single,) }, - _ => { - let last_ty = &type_params[type_params.len() - 1]; - let mut result = quote! { (&'a mut #last_ty,) }; - for ty in type_params.iter().rev().skip(1) { - result = quote! { (&'a mut #ty, #result) }; - } - result - } - } -} - -/// Generate the nest implementation that builds nested structure from flat tuple -fn generate_nest_impl(size: usize) -> proc_macro2::TokenStream { - match size { - 0 => quote! { () }, - 1 => quote! { self }, - _ => { - // Build nested structure: (self.0, (self.1, (self.2, (...(self.N-1,))))) - // Start with innermost tuple (self.{N-1},) and build outwards - let last_index = syn::Index::from(size - 1); - (0..size) - .rev() - .skip(1) - .fold(quote! { (self.#last_index,) }, |acc, i| { - let index = syn::Index::from(i); - quote! { (self.#index, #acc) } - }) - } - } -} - -fn generate_nest_ref_impl(size: usize) -> proc_macro2::TokenStream { - match size { - 0 => quote! { () }, - 1 => quote! { (&self.0,) }, - _ => { - let last_index = syn::Index::from(size - 1); - (0..size) - .rev() - .skip(1) - .fold(quote! { (&self.#last_index,) }, |acc, i| { - let index = syn::Index::from(i); - quote! { (&self.#index, #acc) } - }) - } - } -} - -fn generate_nest_mut_impl(size: usize) -> proc_macro2::TokenStream { - match size { - 0 => quote! { () }, - 1 => quote! { (&mut self.0,) }, - _ => { - let last_index = syn::Index::from(size - 1); - (0..size) - .rev() - .skip(1) - .fold(quote! { (&mut self.#last_index,) }, |acc, i| { - let index = syn::Index::from(i); - quote! { (&mut self.#index, #acc) } - }) - } - } -} diff --git a/tuplities-derive/src/tuplities_option.rs b/tuplities-derive/src/tuplities_option.rs deleted file mode 100644 index 69aac39..0000000 --- a/tuplities-derive/src/tuplities_option.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Submodule providing the derive macro for the `TupleOption` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleOption` trait implementations for all tuple sizes. -pub fn impl_tuple_option() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params,)*> TupleOption for (#(Option<#type_params>,)*) - { - type Transposed = (#(#type_params,)* ); - - #[inline] - fn transpose(self) -> Option { - Some((#(self.#indices?,)*)) - } - } - } - }) -} - -/// Generate `IntoTupleOption` trait implementations for all tuple sizes. -pub fn impl_into_tuple_option() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params,)*> IntoTupleOption for (#(#type_params,)*) - { - type IntoOptions = (#(Option<#type_params>,)*); - - #[inline] - fn into_options(self) -> Self::IntoOptions { - ( #(Some(self.#indices),)* ) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_ord.rs b/tuplities-derive/src/tuplities_ord.rs deleted file mode 100644 index da20f20..0000000 --- a/tuplities-derive/src/tuplities_ord.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Submodule providing the derive macro for the `TupleOrd` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleOrd` trait implementations for all tuple sizes. -pub fn impl_tuple_ord() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: Ord,)*> TupleOrd for (#(#type_params,)*) - { - #[inline] - fn tuple_cmp(&self, other: &Self) -> core::cmp::Ordering { - #( - match self.#indices.cmp(&other.#indices) { - core::cmp::Ordering::Equal => {}, - non_eq => return non_eq, - } - )* - core::cmp::Ordering::Equal - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_partial_eq.rs b/tuplities-derive/src/tuplities_partial_eq.rs deleted file mode 100644 index 2fdc623..0000000 --- a/tuplities-derive/src/tuplities_partial_eq.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Submodule providing the derive macro for the `TuplePartialEq` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TuplePartialEq` trait implementations for all tuple sizes. -pub fn impl_tuple_partial_eq() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - let body = if size == 0 { - quote! { true } - } else { - quote! { #(self.#indices == other.#indices)&&* } - }; - - quote! { - impl<#(#type_params: PartialEq,)*> TuplePartialEq for (#(#type_params,)*) - { - #[inline] - fn tuple_eq(&self, other: &Self) -> bool { - #body - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_partial_ord.rs b/tuplities-derive/src/tuplities_partial_ord.rs deleted file mode 100644 index 6bd1acd..0000000 --- a/tuplities-derive/src/tuplities_partial_ord.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Submodule providing the derive macro for the `TuplePartialOrd` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TuplePartialOrd` trait implementations for all tuple sizes. -pub fn impl_tuple_partial_ord() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: PartialOrd,)*> TuplePartialOrd for (#(#type_params,)*) - { - #[inline] - fn tuple_partial_cmp(&self, other: &Self) -> Option { - #( - match self.#indices.partial_cmp(&other.#indices) { - Some(core::cmp::Ordering::Equal) => {}, - other => return other - } - )* - Some(core::cmp::Ordering::Equal) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_pop_back.rs b/tuplities-derive/src/tuplities_pop_back.rs deleted file mode 100644 index f3696a4..0000000 --- a/tuplities-derive/src/tuplities_pop_back.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Submodule providing the derive macro for the `TuplePopBack` trait. - -use quote::quote; - -use crate::tuple_size::{generate_non_empty, indices, type_params}; - -/// Generate `TuplePopBack` trait implementations for all tuple sizes starting from 1. -pub fn impl_pop_back() -> proc_macro2::TokenStream { - generate_non_empty(|size| { - let type_params = type_params(size); - let indices = indices(size); - let (last, others) = type_params.split_last().unwrap(); - let (last_index, other_indices) = indices.split_last().unwrap(); - - quote! { - impl<#(#type_params,)*> TuplePopBack for (#(#type_params,)*) - { - type Init = (#(#others,)*); - type Back = #last; - - #[inline] - fn pop_back(self) -> (Self::Init, Self::Back) { - ((#(self.#other_indices,)*), self.#last_index) - } - } - - impl<#(#type_params,)*> TupleRefBack for (#(#type_params,)*) - { - #[inline] - fn ref_back(&self) -> &Self::Back { - &self.#last_index - } - } - - impl<#(#type_params,)*> TupleMutBack for (#(#type_params,)*) - { - #[inline] - fn mut_back(&mut self) -> &mut Self::Back { - &mut self.#last_index - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_pop_front.rs b/tuplities-derive/src/tuplities_pop_front.rs deleted file mode 100644 index f45c52c..0000000 --- a/tuplities-derive/src/tuplities_pop_front.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Submodule providing the derive macro for the `TuplePopFront` trait. - -use quote::quote; - -use crate::tuple_size::{generate_non_empty, indices, type_params}; - -/// Generate `TuplePopFront` trait implementations for all tuple sizes starting from 1. -pub fn impl_pop_front() -> proc_macro2::TokenStream { - generate_non_empty(|size| { - let type_params = type_params(size); - let indices = indices(size); - let (first, others) = type_params.split_first().unwrap(); - let (first_index, other_indices) = indices.split_first().unwrap(); - - quote! { - impl<#(#type_params,)*> TuplePopFront for (#(#type_params,)*) - { - type Front = #first; - type Tail = (#(#others,)*); - - #[inline] - fn pop_front(self) -> (Self::Front, Self::Tail) { - (self.#first_index, (#(self.#other_indices,)*)) - } - } - - impl<#(#type_params,)*> TupleRefFront for (#(#type_params,)*) - { - #[inline] - fn ref_front(&self) -> &Self::Front { - &self.#first_index - } - } - - impl<#(#type_params,)*> TupleMutFront for (#(#type_params,)*) - { - #[inline] - fn mut_front(&mut self) -> &mut Self::Front { - &mut self.#first_index - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_push_back.rs b/tuplities-derive/src/tuplities_push_back.rs deleted file mode 100644 index b587a1f..0000000 --- a/tuplities-derive/src/tuplities_push_back.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Submodule providing the derive macro for the `TuplePushBack` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TuplePushBack` trait implementations for all tuple sizes. -pub fn impl_push_back() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl TuplePushBack for (#(#type_params,)*) - { - type Output = (#(#type_params,)* T,); - - fn push_back(self, value: T) -> Self::Output { - (#(self.#indices,)* value,) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_push_front.rs b/tuplities-derive/src/tuplities_push_front.rs deleted file mode 100644 index 9eac61a..0000000 --- a/tuplities-derive/src/tuplities_push_front.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Submodule providing the derive macro for the `TuplePushFront` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TuplePushFront` trait implementations for all tuple sizes. -pub fn impl_push_front() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl TuplePushFront for (#(#type_params,)*) - { - type Output = (T, #(#type_params,)*); - - #[inline] - fn push_front(self, value: T) -> Self::Output { - (value, #(self.#indices,)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_ref.rs b/tuplities-derive/src/tuplities_ref.rs deleted file mode 100644 index 8e84443..0000000 --- a/tuplities-derive/src/tuplities_ref.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Submodule providing the derive macro for the `TupleRef` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleRef` trait implementations for all tuple sizes. -pub fn impl_tuple_ref() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params,)*> TupleRef for (#(#type_params,)*) - { - type Ref<'a> = (#(&'a #type_params,)*) where Self: 'a; - - #[inline] - fn tuple_ref(&self) -> Self::Ref<'_> { - (#(&self.#indices,)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_ref_map.rs b/tuplities-derive/src/tuplities_ref_map.rs deleted file mode 100644 index 1dcd23a..0000000 --- a/tuplities-derive/src/tuplities_ref_map.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Submodule providing the derive macro for the `TupleRefMap` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleRefMap` trait implementations for all tuple sizes. -pub fn impl_tuple_ref_map() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl<#(#type_params: TupleRef,)*> TupleRefMap for (#(#type_params,)*) - { - type RefMap<'a> = (#(< #type_params as TupleRef >::Ref<'a>,)*) where Self: 'a; - - #[inline] - fn tuple_ref_map(&self) -> Self::RefMap<'_> { - (#(self.#indices.tuple_ref(),)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_remove.rs b/tuplities-derive/src/tuplities_remove.rs deleted file mode 100644 index 38361c7..0000000 --- a/tuplities-derive/src/tuplities_remove.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Submodule providing the derive macro for the `TupleRemove` trait. - -use quote::quote; - -use crate::tuple_size::{generate_non_empty, type_params}; - -/// Generate `TupleRemove` trait implementations for all tuple sizes and indices. -pub fn impl_remove() -> proc_macro2::TokenStream { - generate_non_empty(|size| { - let type_params = type_params(size); - (0..size) - .map(|u_idx| { - let removed_type = &type_params[u_idx]; - let others = type_params - .iter() - .enumerate() - .filter_map(|(i, t)| if i == u_idx { None } else { Some(t) }) - .collect::>(); - let removed_index = syn::Index::from(u_idx); - let other_indices = (0..size) - .filter_map(|i| { - if i == u_idx { - None - } else { - Some(syn::Index::from(i)) - } - }) - .collect::>(); - let typenum_ident = - syn::Ident::new(&format!("U{u_idx}"), proc_macro2::Span::call_site()); - let typenum: syn::Path = syn::parse_quote!(typenum::#typenum_ident); - quote! { - impl<#(#type_params,)*> TupleRemove<#typenum> for (#(#type_params,)*) - { - type Type = #removed_type; - type Remainder = (#(#others,)*); - - #[inline] - fn remove(self) -> (Self::Type, Self::Remainder) { - (self.#removed_index, (#(self.#other_indices,)*)) - } - } - } - }) - .collect() - }) -} diff --git a/tuplities-derive/src/tuplities_replicate.rs b/tuplities-derive/src/tuplities_replicate.rs deleted file mode 100644 index 866a08c..0000000 --- a/tuplities-derive/src/tuplities_replicate.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Submodule providing the derive macro for the `TupleReplicate` trait. - -use quote::quote; - -use crate::tuple_size::generate_all_sizes; - -/// Generate `TupleReplicate` trait implementations for all tuple sizes. -pub fn impl_tuple_replicate() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - if size == 0 { - // Empty tuple: no Clone bound needed - return quote! { - impl TupleReplicate for () { - #[inline] - fn tuple_replicate(_value: T) -> Self { - () - } - } - }; - } - - if size == 1 { - // Single element: no Clone bound needed, just move the value - return quote! { - impl TupleReplicate for (T,) { - #[inline] - fn tuple_replicate(value: T) -> Self { - (value,) - } - } - }; - } - - let tuple_elements = (0..size).map(|_| quote! { T }).collect::>(); - - // Multiple elements: Clone bound needed, clone for all but last, move for last - let mut replicated_values = Vec::new(); - for i in 0..size { - if i == size - 1 { - replicated_values.push(quote! { value }); - } else { - replicated_values.push(quote! { value.clone() }); - } - } - - quote! { - impl<'a, 'b, T: Clone> TupleReplicate for (#(#tuple_elements,)*) - where - T: 'a, - Self: 'b, - 'a: 'b, - { - fn tuple_replicate(value: T) -> Self { - (#(#replicated_values,)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_reverse.rs b/tuplities-derive/src/tuplities_reverse.rs deleted file mode 100644 index 13b95d8..0000000 --- a/tuplities-derive/src/tuplities_reverse.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Submodule providing the derive macro for the `TupleReverse` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleReverse` trait implementations for all tuple sizes. -pub fn impl_reverse() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - let reversed_indices: Vec<_> = indices.iter().rev().collect(); - let reversed_type_params: Vec<_> = type_params.iter().rev().collect(); - - quote! { - impl<#(#type_params,)*> TupleReverse for (#(#type_params,)*) - { - type Output = (#(#reversed_type_params,)*); - - #[inline] - fn reverse(self) -> Self::Output { - (#(self.#reversed_indices,)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_row.rs b/tuplities-derive/src/tuplities_row.rs deleted file mode 100644 index 9cedbb6..0000000 --- a/tuplities-derive/src/tuplities_row.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Submodule providing the derive macro for the `TupleRow` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleRow` trait implementations for all tuple sizes. -pub fn impl_row() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl,)*> TupleRow for (#(#type_params,)*) - { - type RowType = (#(#type_params::Element,)*); - - fn tuple_row(&self) -> ::Ref<'_> { - (#(self.#indices.tuple_index(),)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_row_mut.rs b/tuplities-derive/src/tuplities_row_mut.rs deleted file mode 100644 index f039534..0000000 --- a/tuplities-derive/src/tuplities_row_mut.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Submodule providing the derive macro for the `TupleRowMut` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleRowMut` trait implementations for all tuple sizes. -pub fn impl_row_mut() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let type_params = type_params(size); - let indices = indices(size); - - quote! { - impl,)*> TupleRowMut for (#(#type_params,)*) - { - fn tuple_row_mut(&mut self) -> ::Mut<'_> { - (#(self.#indices.tuple_index_mut(),)*) - } - } - } - }) -} diff --git a/tuplities-derive/src/tuplities_split.rs b/tuplities-derive/src/tuplities_split.rs deleted file mode 100644 index 0adf7cb..0000000 --- a/tuplities-derive/src/tuplities_split.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Submodule providing the derive macro for the `TupleSplit` trait. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params}; - -/// Generate `TupleSplit` trait implementations for all tuple sizes and indices. -pub fn impl_split() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - (0..=size) - .map(|u_idx| { - let type_params = type_params(size); - let indices = indices(size); - let left_types = &type_params[..u_idx]; - let right_types = &type_params[u_idx..]; - let left_indices = &indices[..u_idx]; - let right_indices = &indices[u_idx..]; - let typenum_ident = - syn::Ident::new(&format!("U{u_idx}"), proc_macro2::Span::call_site()); - let typenum: syn::Path = syn::parse_quote!(typenum::#typenum_ident); - quote! { - impl<#(#type_params,)*> TupleSplit<#typenum> for (#(#type_params,)*) - { - type Left = (#(#left_types,)*); - type Right = (#(#right_types,)*); - - #[inline] - fn split(self) -> (Self::Left, Self::Right) { - ((#(self.#left_indices,)*), (#(self.#right_indices,)*)) - } - } - } - }) - .collect() - }) -} diff --git a/tuplities-derive/src/tuplities_try_from.rs b/tuplities-derive/src/tuplities_try_from.rs deleted file mode 100644 index 2eed50d..0000000 --- a/tuplities-derive/src/tuplities_try_from.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Submodule providing the derive macro for the `TupleTryFrom` and `TupleTryInto` traits. - -use quote::quote; - -use crate::tuple_size::{generate_all_sizes, indices, type_params, type_params_with_prefix}; - -/// Generate `TupleTryFrom` trait implementations for all tuple sizes. -pub fn impl_tuple_try_from() -> proc_macro2::TokenStream { - generate_all_sizes(|size| { - let source_type_params = type_params(size); - let target_type_params = type_params_with_prefix(size, "B"); - let indices = indices(size); - - if size == 0 { - return quote! { - impl TupleTryFrom<(), E> for () { - fn tuple_try_from(_value: ()) -> Result { - Ok(()) - } - } - }; - } - - let source_tuple = quote! { (#(#source_type_params,)*) }; - - quote! { - impl<#(#source_type_params,)* #(#target_type_params,)* E> TupleTryFrom<#source_tuple, E> for (#(#target_type_params,)*) - where - #(#target_type_params: core::convert::TryFrom<#source_type_params>,)* - E: #(From<<#target_type_params as core::convert::TryFrom<#source_type_params>>::Error>)+* - { - fn tuple_try_from(value: #source_tuple) -> Result { - Ok((#(#target_type_params::try_from(value.#indices)?,)*)) - } - } - } - }) -} diff --git a/tuplities-eq/Cargo.toml b/tuplities-eq/Cargo.toml deleted file mode 100644 index 6881f6f..0000000 --- a/tuplities-eq/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-eq" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for comparing tuples for equality" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - -[dependencies] -tuplities-derive.workspace = true -tuplities-partial-eq.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16", "tuplities-partial-eq/size-16"] -size-32 = ["tuplities-derive/size-32", "tuplities-partial-eq/size-32"] -size-48 = ["tuplities-derive/size-48", "tuplities-partial-eq/size-48"] -size-64 = ["tuplities-derive/size-64", "tuplities-partial-eq/size-64"] -size-96 = ["tuplities-derive/size-96", "tuplities-partial-eq/size-96"] -size-128 = ["tuplities-derive/size-128", "tuplities-partial-eq/size-128"] diff --git a/tuplities-eq/src/lib.rs b/tuplities-eq/src/lib.rs deleted file mode 100644 index a87517f..0000000 --- a/tuplities-eq/src/lib.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleEq` trait. - -use tuplities_partial_eq::TuplePartialEq; - -#[tuplities_derive::impl_tuple_eq] -/// A trait for comparing tuples for total equality. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleEq: TuplePartialEq { - /// Returns `true` if `self` and `other` are equal. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_eq::TupleEq; - /// - /// let tuple1 = (1, "hello"); - /// let tuple2 = (1, "hello"); - /// let tuple3 = (2, "world"); - /// - /// assert!(tuple1.tuple_eq(&tuple2)); - /// assert!(!tuple1.tuple_eq(&tuple3)); - /// ``` - fn tuple_eq(&self, other: &Self) -> bool; -} diff --git a/tuplities-flatten-nest/Cargo.toml b/tuplities-flatten-nest/Cargo.toml deleted file mode 100644 index 8d0570b..0000000 --- a/tuplities-flatten-nest/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "tuplities-flatten-nest" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "Traits for flattening nested tuples and nesting flat tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true -tuplities-push-front.workspace = true -typenum.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-flatten-nest/src/lib.rs b/tuplities-flatten-nest/src/lib.rs deleted file mode 100644 index 249d6ef..0000000 --- a/tuplities-flatten-nest/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing traits for flattening and nesting tuples. - -extern crate alloc; - -mod flatten_nested; -mod matrix; -mod nest; -mod nested_chain; -mod nested_index; -mod nested_into_vec; -mod nested_option; -mod nested_option_try_from; -mod nested_push_pop; -mod nested_ref; -mod nested_replicate; -mod nested_row; -mod nested_starts_with; -mod nested_try_from; - -// Re-export all public traits and types -pub use flatten_nested::FlattenNestedTuple; -pub use matrix::{ - FlattenMatrixElements, FlattenNestedTupleMatrix, NestMatrixElements, NestTupleMatrix, -}; -pub use nest::{NestTuple, NestTupleMut, NestTupleRef}; -pub use nested_chain::NestedTupleChain; -pub use nested_index::{NestedTupleIndex, NestedTupleIndexMut}; -pub use nested_into_vec::NestedTupleIntoVec; -pub use nested_option::{ - IntoNestedTupleOption, NestedTupleFlattenOption, NestedTupleOption, NestedTupleOptionWith, -}; -pub use nested_option_try_from::{ - NestedTupleOptionFrom, NestedTupleOptionInto, NestedTupleOptionTryFrom, - NestedTupleOptionTryInto, -}; -pub use nested_push_pop::{ - NestedTuplePopBack, NestedTuplePopFront, NestedTuplePushBack, NestedTuplePushFront, -}; -pub use nested_ref::{NestedTupleMut, NestedTupleRef}; -pub use nested_replicate::NestedTupleReplicate; -pub use nested_row::{NestedTupleRow, NestedTupleRowMut}; -pub use nested_starts_with::NestedTupleStartsWith; -pub use nested_try_from::{NestedTupleFrom, NestedTupleInto}; -pub use nested_try_from::{NestedTupleTryFrom, NestedTupleTryInto}; diff --git a/tuplities-from/Cargo.toml b/tuplities-from/Cargo.toml deleted file mode 100644 index 13300c7..0000000 --- a/tuplities-from/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "tuplities-from" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "Traits for converting values into tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - -[dependencies] -tuplities-derive.workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-from/src/lib.rs b/tuplities-from/src/lib.rs deleted file mode 100644 index e048093..0000000 --- a/tuplities-from/src/lib.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleFrom` and `TupleInto` traits. - -#[tuplities_derive::impl_tuple_from] -/// A trait for infallibly converting from one tuple type to another. -/// -/// This trait allows converting between tuples where each element can be converted -/// using the standard `From` trait. Unlike `TupleTryFrom`, this trait guarantees -/// that the conversion will succeed. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleFrom { - /// Converts from `T` into `Self`. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_from::TupleFrom; - /// - /// let source = (1u8, 2u8); - /// let target: (u32, u32) = TupleFrom::tuple_from(source); - /// assert_eq!(target, (1, 2)); - /// ``` - fn tuple_from(value: T) -> Self; -} - -/// A trait for infallibly converting a tuple into another tuple type. -/// -/// This trait allows converting tuples into other tuple types where each element can be converted -/// using the standard `From` trait. Unlike `TupleTryInto`, this trait guarantees -/// that the conversion will succeed. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleInto { - /// Converts `self` into `T`. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_from::TupleInto; - /// - /// let source = (1u8, 2u8); - /// let target: (u32, u32) = TupleInto::tuple_into(source); - /// assert_eq!(target, (1, 2)); - /// ``` - fn tuple_into(self) -> T; -} - -// Blanket implementation of TupleInto based on TupleFrom -impl TupleInto for U -where - T: TupleFrom, -{ - fn tuple_into(self) -> T { - T::tuple_from(self) - } -} diff --git a/tuplities-hash/Cargo.toml b/tuplities-hash/Cargo.toml deleted file mode 100644 index 9725b7d..0000000 --- a/tuplities-hash/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "tuplities-hash" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for hashing tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true -tuplities-eq.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16", "tuplities-eq/size-16"] -size-32 = ["tuplities-derive/size-32", "tuplities-eq/size-32"] -size-48 = ["tuplities-derive/size-48", "tuplities-eq/size-48"] -size-64 = ["tuplities-derive/size-64", "tuplities-eq/size-64"] -size-96 = ["tuplities-derive/size-96", "tuplities-eq/size-96"] -size-128 = ["tuplities-derive/size-128", "tuplities-eq/size-128"] diff --git a/tuplities-hash/src/lib.rs b/tuplities-hash/src/lib.rs deleted file mode 100644 index 34f8a40..0000000 --- a/tuplities-hash/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleHash` trait. - -use tuplities_eq::TupleEq; - -#[tuplities_derive::impl_tuple_hash] -/// A trait for hashing tuples with a generic hasher. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleHash: TupleEq { - /// Hashes the tuple into the given hasher. - fn tuple_hash(&self, state: &mut H); -} diff --git a/tuplities-index/Cargo.toml b/tuplities-index/Cargo.toml deleted file mode 100644 index d340eb8..0000000 --- a/tuplities-index/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "tuplities-index" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "Traits for compile-time indexing into tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -typenum = { workspace = true } -tuplities-derive = { workspace = true } -tuplities-len = { workspace = true } - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] - -[lints] -workspace = true diff --git a/tuplities-index/src/lib.rs b/tuplities-index/src/lib.rs deleted file mode 100644 index 461c918..0000000 --- a/tuplities-index/src/lib.rs +++ /dev/null @@ -1,119 +0,0 @@ -//! A trait for indexing into tuples at compile-time known positions. -//! -//! This crate provides the `TupleIndex` trait, which allows accessing -//! elements at compile-time known indices from tuples using `typenum::Unsigned` types. - -#![no_std] - -use tuplities_len::TupleLen; - -/// A trait for indexing into tuples at compile-time known positions. -/// -/// This trait allows accessing elements at specific indices `Idx` -/// from tuples, where `Idx` is a compile-time constant from `typenum`. -/// -/// # Examples -/// -/// ``` -/// use tuplities_index::TupleIndex; -/// use typenum::U1; -/// -/// let tuple = (1, "hello", 3.14); -/// let element = TupleIndex::::tuple_index(&tuple); -/// assert_eq!(*element, "hello"); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -#[tuplities_derive::impl_tuple_index] -pub trait TupleIndex: TupleLen { - /// The type of the element at index `Idx`. - type Element; - - /// Returns a reference to the element at index `Idx`. - fn tuple_index(&self) -> &Self::Element; -} - -/// A trait for mutable indexing into tuples at compile-time known positions. -/// -/// This trait allows mutable access to elements at specific indices `Idx` -/// from tuples, where `Idx` is a compile-time constant from `typenum`. -/// -/// # Examples -/// -/// ``` -/// use tuplities_index::TupleIndexMut; -/// use typenum::U1; -/// -/// let mut tuple = (1, "hello", 3.14); -/// let element = TupleIndexMut::::tuple_index_mut(&mut tuple); -/// *element = "world"; -/// assert_eq!(tuple.1, "world"); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleIndexMut: TupleIndex { - /// Returns a mutable reference to the element at index `Idx`. - fn tuple_index_mut(&mut self) -> &mut Self::Element; -} - -/// A convenience trait for accessing the first element (index 0) in tuples. -/// -/// This trait is automatically implemented for any tuple that implements `TupleIndex`. -/// -/// # Examples -/// -/// ``` -/// use tuplities_index::FirstTupleIndex; -/// -/// let tuple = (1, "hello", 3.14); -/// let first = tuple.first_tuple_index(); -/// assert_eq!(*first, 1); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait FirstTupleIndex: TupleIndex { - /// Returns a reference to the first element in the tuple. - fn first_tuple_index(&self) -> &Self::Element { - self.tuple_index() - } -} - -impl> FirstTupleIndex for T {} - -/// A convenience trait for accessing the last element in tuples. -/// -/// This trait is automatically implemented for any tuple that implements `TupleIndex` -/// where `LastIndex` is calculated as `TupleLen::Len - 1`. -/// -/// # Examples -/// -/// ``` -/// use tuplities_index::LastTupleIndex; -/// -/// let tuple = (1, "hello", 3.14); -/// let last = tuple.last_tuple_index(); -/// assert_eq!(*last, 3.14); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait LastTupleIndex: - TupleIndex< - <::Len as core::ops::Sub>::Output, - Len: core::ops::Sub, - > -{ - /// Returns a reference to the last element in the tuple. - fn last_tuple_index(&self) -> &Self::Element; -} - -impl LastTupleIndex for T -where - T: TupleIndex< - <::Len as core::ops::Sub>::Output, - Len: core::ops::Sub, - >, -{ - fn last_tuple_index(&self) -> &Self::Element { - self.tuple_index() - } -} diff --git a/tuplities-insert/Cargo.toml b/tuplities-insert/Cargo.toml deleted file mode 100644 index df166e3..0000000 --- a/tuplities-insert/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "tuplities-insert" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for inserting elements into tuples at compile-time indices" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -typenum = { workspace = true } -tuplities-derive = { workspace = true } - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] - -[lints] -workspace = true diff --git a/tuplities-insert/src/lib.rs b/tuplities-insert/src/lib.rs deleted file mode 100644 index 3608ba3..0000000 --- a/tuplities-insert/src/lib.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! A trait for inserting elements at specific indices into tuples. -//! -//! This crate provides the `TupleInsert` trait, which allows inserting an element -//! at a compile-time known index into a tuple, returning the tuple with the element inserted. - -#![no_std] - -/// A trait for inserting an element at a specific index into a tuple. -/// -/// This trait allows inserting an element at compile-time known index `Idx` -/// into a tuple, returning the tuple with the element inserted. -/// -/// # Examples -/// -/// ``` -/// use tuplities_insert::TupleInsert; -/// use typenum::U1; -/// -/// let tuple = (1, 3.14); -/// let inserted = TupleInsert::::insert(tuple, "hello"); -/// assert_eq!(inserted, (1, "hello", 3.14)); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -#[tuplities_derive::impl_insert] -pub trait TupleInsert { - /// The type of the tuple after inserting the element. - type Output; - - /// Inserts the element at index `Idx` into the tuple. - /// - /// Returns the tuple with the element inserted at the specified index. - fn insert(self, value: T) -> Self::Output; -} - -#[cfg(test)] -mod tests { - use super::TupleInsert; - use typenum::{U0, U1, U2}; - - #[test] - fn test_insert_zero_elements() { - let tuple = (); - let result = TupleInsert::::insert(tuple, 42); - // () insert at U0 -> (i32,) - let expected: (i32,) = (42,); - assert_eq!(result, expected); - } - - #[test] - fn test_insert_single_element() { - let tuple = (1,); - // Insert at U0 - let result0 = TupleInsert::::insert(tuple, 42); - let expected0: (i32, i32) = (42, 1); - assert_eq!(result0, expected0); - - // Insert at U1 - let tuple = (1,); - let result1 = TupleInsert::::insert(tuple, 42); - let expected1: (i32, i32) = (1, 42); - assert_eq!(result1, expected1); - } - - #[test] - fn test_insert_two_elements() { - let tuple = (1, 2); - // Insert at U0 - let result0 = TupleInsert::::insert(tuple, 42); - let expected0: (i32, i32, i32) = (42, 1, 2); - assert_eq!(result0, expected0); - - // Insert at U1 - let tuple = (1, 2); - let result1 = TupleInsert::::insert(tuple, 42); - let expected1: (i32, i32, i32) = (1, 42, 2); - assert_eq!(result1, expected1); - - // Insert at U2 - let tuple = (1, 2); - let result2 = TupleInsert::::insert(tuple, 42); - let expected2: (i32, i32, i32) = (1, 2, 42); - assert_eq!(result2, expected2); - } -} diff --git a/tuplities-len/Cargo.toml b/tuplities-len/Cargo.toml deleted file mode 100644 index a451ecc..0000000 --- a/tuplities-len/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "tuplities-len" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for getting the compile-time length of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -typenum = { workspace = true } -tuplities-derive = { workspace = true } - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] - -[lints] -workspace = true diff --git a/tuplities-len/src/lib.rs b/tuplities-len/src/lib.rs deleted file mode 100644 index 14960c7..0000000 --- a/tuplities-len/src/lib.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! A trait for getting the length of tuples at compile time. -//! -//! This crate provides the `TupleLen` trait, which allows getting the length -//! of a tuple as a compile-time `typenum::Unsigned` type. - -#![no_std] - -/// A trait for getting the compile-time length of a tuple. -/// -/// This trait provides the length of a tuple as an associated type `Len` -/// that implements `typenum::Unsigned`. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -#[tuplities_derive::impl_len] -pub trait TupleLen { - /// The length of the tuple as a `typenum::Unsigned` type. - type Len: typenum::Unsigned; -} - -/// A marker trait for empty tuples (size 0). -/// -/// This trait is implemented for the unit type `()` and can be used -/// for type-level programming to identify empty tuples. -/// -/// # Examples -/// -/// ``` -/// use tuplities_len::{UnitTuple, TupleLen}; -/// use typenum::U0; -/// -/// fn is_empty(_tuple: T) { -/// // This function only accepts empty tuples -/// } -/// -/// is_empty(()); // This works -/// // is_empty((1,)); // This would not compile -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait UnitTuple: TupleLen {} - -impl UnitTuple for () {} - -/// A marker trait for single-element tuples (size 1). -/// -/// This trait is implemented for single-element tuples `(T,)` and can be used -/// for type-level programming to identify single-element tuples. -/// -/// # Examples -/// -/// ``` -/// use tuplities_len::{SingletonTuple, TupleLen}; -/// use typenum::U1; -/// -/// fn is_single(_tuple: T) { -/// // This function only accepts single-element tuples -/// } -/// -/// is_single((42,)); // This works -/// // is_single((1, 2)); // This would not compile -/// // is_single(()); // This would not compile -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait SingletonTuple: TupleLen {} - -impl SingletonTuple for (T,) {} - -/// A marker trait for two-element tuples (size 2). -/// -/// This trait is implemented for two-element tuples `(T1, T2)` and can be used -/// for type-level programming to identify two-element tuples. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_len::{PairTuple, TupleLen}; -/// use typenum::U2; -/// fn is_pair(_tuple: T) { -/// // This function only accepts two-element tuples -/// } -/// is_pair((1, 2)); // This works -/// // is_pair((42,)); // This would not compile -/// // is_pair(()); // This would not compile -/// ``` -/// -pub trait PairTuple: TupleLen {} - -impl PairTuple for (T1, T2) {} diff --git a/tuplities-mut/Cargo.toml b/tuplities-mut/Cargo.toml deleted file mode 100644 index 6ef74e6..0000000 --- a/tuplities-mut/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-mut" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for getting tuples of mutable references from tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-mut/src/lib.rs b/tuplities-mut/src/lib.rs deleted file mode 100644 index e0edeeb..0000000 --- a/tuplities-mut/src/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleMut` trait. - -#[tuplities_derive::impl_tuple_mut] -/// A trait for tuples that provides a method to get a tuple of mutable references. -/// -/// This trait provides both an associated type `Mut<'a>` that represents a tuple -/// of mutable references to the elements, and a method `tuple_mut` that returns such a tuple. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_mut::TupleMut; -/// -/// let mut tuple = (1, "hello".to_string(), vec![1, 2, 3]); -/// let mut_refs = tuple.tuple_mut(); -/// *mut_refs.0 = 42; -/// mut_refs.1.push_str(" world"); -/// mut_refs.2.push(4); -/// assert_eq!(tuple, (42, "hello world".to_string(), vec![1, 2, 3, 4])); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleMut { - /// The type of a tuple containing mutable references to each element. - type Mut<'a> - where - Self: 'a; - - /// Returns a tuple of mutable references to each element. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_mut::TupleMut; - /// - /// let mut tuple = (42, "world".to_string()); - /// let mut_refs = tuple.tuple_mut(); - /// *mut_refs.0 = 24; - /// mut_refs.1.make_ascii_uppercase(); - /// assert_eq!(tuple, (24, "WORLD".to_string())); - /// ``` - fn tuple_mut(&mut self) -> Self::Mut<'_>; -} - -#[tuplities_derive::impl_tuple_mut_map] -/// A trait for applying `TupleMut` to each element of a tuple. -/// -/// This trait takes a mutable reference to a tuple where each element implements `TupleMut` and returns -/// a tuple where each element is the result of calling `tuple_mut()` on the original elements. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_mut::TupleMutMap; -/// -/// let mut matrix = ((1, 2), (3, 4), (5, 6)); -/// let mut_ref_matrix = matrix.tuple_mut_map(); -/// assert_eq!(mut_ref_matrix, ((&mut 1, &mut 2), (&mut 3, &mut 4), (&mut 5, &mut 6))); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleMutMap { - /// The type of a tuple containing tuples of mutable references to each inner element. - type MutMap<'a> - where - Self: 'a; - - /// Returns a tuple where each element is a tuple of mutable references to the inner elements. - fn tuple_mut_map(&mut self) -> Self::MutMap<'_>; -} diff --git a/tuplities-option/Cargo.toml b/tuplities-option/Cargo.toml deleted file mode 100644 index 67c7ee5..0000000 --- a/tuplities-option/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-option" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "Traits for working with tuples of Options" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - -[dependencies] -tuplities-derive.workspace = true -tuplities-default.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16", "tuplities-default/size-16"] -size-32 = ["tuplities-derive/size-32", "tuplities-default/size-32"] -size-48 = ["tuplities-derive/size-48", "tuplities-default/size-48"] -size-64 = ["tuplities-derive/size-64", "tuplities-default/size-64"] -size-96 = ["tuplities-derive/size-96", "tuplities-default/size-96"] -size-128 = ["tuplities-derive/size-128", "tuplities-default/size-128"] diff --git a/tuplities-option/src/lib.rs b/tuplities-option/src/lib.rs deleted file mode 100644 index e73ebc3..0000000 --- a/tuplities-option/src/lib.rs +++ /dev/null @@ -1,54 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleOption` and `IntoTupleOption` traits. - -use tuplities_default::TupleDefault; - -#[tuplities_derive::impl_tuple_option] -/// A trait for transposing a tuple of options into an option of a tuple. -pub trait TupleOption: TupleDefault { - /// The transposed type: an option of the tuple of the inner types. - type Transposed: IntoTupleOption; - - /// Transposes the tuple of options into an option of the tuple. - /// - /// Returns `Some((a, b, ...))` if all elements are `Some`, otherwise `None`. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_option::TupleOption; - /// - /// let tuple = (Some(1), Some(2)); - /// let transposed: Option<(i32, i32)> = tuple.transpose(); - /// assert_eq!(transposed, Some((1, 2))); - /// - /// let tuple = (Some(1), None); - /// let transposed: Option<(i32, i32)> = tuple.transpose(); - /// assert_eq!(transposed, None); - /// ``` - /// - /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. - fn transpose(self) -> Option; -} - -/// A trait for converting a tuple into a tuple of options. -pub trait IntoTupleOption { - /// The tuple of options type. - type IntoOptions: TupleOption + TupleDefault; - - /// Converts the tuple into a tuple of `Some` values. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_option::IntoTupleOption; - /// - /// let tuple = (1, 2); - /// let into_options: (Option, Option) = tuple.into_options(); - /// assert_eq!(into_options, (Some(1), Some(2))); - /// ``` - /// - /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. - fn into_options(self) -> Self::IntoOptions; -} diff --git a/tuplities-ord/Cargo.toml b/tuplities-ord/Cargo.toml deleted file mode 100644 index c79d627..0000000 --- a/tuplities-ord/Cargo.toml +++ /dev/null @@ -1,54 +0,0 @@ -[package] -name = "tuplities-ord" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for total ordering comparison of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true -tuplities-eq.workspace = true -tuplities-partial-ord.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = [ - "tuplities-derive/size-16", - "tuplities-eq/size-16", - "tuplities-partial-ord/size-16", -] -size-32 = [ - "tuplities-derive/size-32", - "tuplities-eq/size-32", - "tuplities-partial-ord/size-32", -] -size-48 = [ - "tuplities-derive/size-48", - "tuplities-eq/size-48", - "tuplities-partial-ord/size-48", -] -size-64 = [ - "tuplities-derive/size-64", - "tuplities-eq/size-64", - "tuplities-partial-ord/size-64", -] -size-96 = [ - "tuplities-derive/size-96", - "tuplities-eq/size-96", - "tuplities-partial-ord/size-96", -] -size-128 = [ - "tuplities-derive/size-128", - "tuplities-eq/size-128", - "tuplities-partial-ord/size-128", -] diff --git a/tuplities-ord/src/lib.rs b/tuplities-ord/src/lib.rs deleted file mode 100644 index f647c50..0000000 --- a/tuplities-ord/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleOrd` trait. - -use tuplities_eq::TupleEq; -use tuplities_partial_ord::TuplePartialOrd; - -#[tuplities_derive::impl_tuple_ord] -/// A trait for comparing tuples for total ordering. -pub trait TupleOrd: TuplePartialOrd + TupleEq { - /// Returns the total ordering of `self` and `other`. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_ord::TupleOrd; - /// use core::cmp::Ordering; - /// - /// let tuple1 = (1, 2); - /// let tuple2 = (1, 3); - /// let tuple3 = (2, 1); - /// - /// assert_eq!(tuple1.tuple_cmp(&tuple2), Ordering::Less); - /// assert_eq!(tuple1.tuple_cmp(&tuple3), Ordering::Less); - /// assert_eq!(tuple1.tuple_cmp(&tuple1), Ordering::Equal); - /// ``` - /// - /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. - fn tuple_cmp(&self, other: &Self) -> core::cmp::Ordering; -} diff --git a/tuplities-partial-eq/Cargo.toml b/tuplities-partial-eq/Cargo.toml deleted file mode 100644 index e00e1f5..0000000 --- a/tuplities-partial-eq/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-partial-eq" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for partial equality comparison of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-partial-eq/src/lib.rs b/tuplities-partial-eq/src/lib.rs deleted file mode 100644 index 36b4deb..0000000 --- a/tuplities-partial-eq/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TuplePartialEq` trait. - -#[tuplities_derive::impl_tuple_partial_eq] -/// A trait for comparing tuples for partial equality. -pub trait TuplePartialEq { - /// Returns `true` if `self` and `other` are equal. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_partial_eq::TuplePartialEq; - /// - /// let tuple1 = (1, "hello"); - /// let tuple2 = (1, "hello"); - /// let tuple3 = (2, "world"); - /// - /// assert!(tuple1.tuple_eq(&tuple2)); - /// assert!(!tuple1.tuple_eq(&tuple3)); - /// ``` - /// - /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. - fn tuple_eq(&self, other: &Self) -> bool; -} diff --git a/tuplities-partial-ord/Cargo.toml b/tuplities-partial-ord/Cargo.toml deleted file mode 100644 index f2f779d..0000000 --- a/tuplities-partial-ord/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "tuplities-partial-ord" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for partial ordering comparison of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true -tuplities-partial-eq.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16", "tuplities-partial-eq/size-16"] -size-32 = ["tuplities-derive/size-32", "tuplities-partial-eq/size-32"] -size-48 = ["tuplities-derive/size-48", "tuplities-partial-eq/size-48"] -size-64 = ["tuplities-derive/size-64", "tuplities-partial-eq/size-64"] -size-96 = ["tuplities-derive/size-96", "tuplities-partial-eq/size-96"] -size-128 = ["tuplities-derive/size-128", "tuplities-partial-eq/size-128"] diff --git a/tuplities-partial-ord/src/lib.rs b/tuplities-partial-ord/src/lib.rs deleted file mode 100644 index ce69922..0000000 --- a/tuplities-partial-ord/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TuplePartialOrd` trait. - -use tuplities_partial_eq::TuplePartialEq; - -#[tuplities_derive::impl_tuple_partial_ord] -/// A trait for comparing tuples for partial ordering. -pub trait TuplePartialOrd: TuplePartialEq { - /// Returns the partial ordering of `self` and `other`. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_partial_ord::TuplePartialOrd; - /// use core::cmp::Ordering; - /// - /// let tuple1 = (1, 2); - /// let tuple2 = (1, 3); - /// let tuple3 = (2, 1); - /// - /// assert_eq!(tuple1.tuple_partial_cmp(&tuple2), Some(Ordering::Less)); - /// assert_eq!(tuple1.tuple_partial_cmp(&tuple3), Some(Ordering::Less)); - /// assert_eq!(tuple1.tuple_partial_cmp(&tuple1), Some(Ordering::Equal)); - /// ``` - /// - /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. - fn tuple_partial_cmp(&self, other: &Self) -> Option; -} diff --git a/tuplities-pop-back/Cargo.toml b/tuplities-pop-back/Cargo.toml deleted file mode 100644 index b9e264d..0000000 --- a/tuplities-pop-back/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-pop-back" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for popping elements from the back of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-pop-back/src/lib.rs b/tuplities-pop-back/src/lib.rs deleted file mode 100644 index 119fd72..0000000 --- a/tuplities-pop-back/src/lib.rs +++ /dev/null @@ -1,95 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TuplePopBack` trait. - -#[tuplities_derive::impl_pop_back] -/// A trait for tuples that defines the `Init` and `Back` types, and provides a method to pop the back element. -pub trait TuplePopBack { - /// The type of the tuple without the last element. - type Init; - - /// The type of the last element. - type Back; - - /// Consumes the tuple and returns the initial part and the last element. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_pop_back::TuplePopBack; - /// - /// let tuple = (1, 2, 3); - /// let (front, back) = tuple.pop_back(); - /// assert_eq!(front, (1, 2)); - /// assert_eq!(back, 3); - /// ``` - /// - /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. - fn pop_back(self) -> (Self::Init, Self::Back); -} - -/// A trait for accessing a reference to the last element of a tuple. -/// -/// This trait provides a method to get a reference to the last element without consuming the tuple. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_pop_back::TupleRefBack; -/// -/// let tuple = (1, 2, 3); -/// let last = tuple.ref_back(); -/// assert_eq!(*last, 3); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleRefBack: TuplePopBack { - /// Returns a reference to the last element of the tuple. - fn ref_back(&self) -> &Self::Back; -} - -/// A trait for accessing a mutable reference to the last element of a tuple. -/// -/// This trait provides a method to get a mutable reference to the last element without consuming the tuple. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_pop_back::TupleMutBack; -/// -/// let mut tuple = (1, 2, 3); -/// let last = tuple.mut_back(); -/// *last = 42; -/// assert_eq!(tuple, (1, 2, 42)); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleMutBack: TupleRefBack { - /// Returns a mutable reference to the last element of the tuple. - fn mut_back(&mut self) -> &mut Self::Back; -} - -#[cfg(test)] -mod tests { - use super::TuplePopBack; - - #[test] - fn test_pop_back_single_element_tuple() { - let tuple = (42,); - let (_init, back): ((), i32) = tuple.pop_back(); - // Check that the types are correct: (T,) -> Init = (), Back = T - let expected_back: i32 = 42; - assert_eq!(back, expected_back); - } - - #[test] - fn test_pop_back_two_element_tuple() { - let tuple = (1, 2); - let (init, back) = tuple.pop_back(); - // Check that the types are correct: (T1, T2) -> Init = (T1,), Back = T2 - let expected_init: (i32,) = (1,); - let expected_back: i32 = 2; - assert_eq!(init, expected_init); - assert_eq!(back, expected_back); - } -} diff --git a/tuplities-pop-front/Cargo.toml b/tuplities-pop-front/Cargo.toml deleted file mode 100644 index 01097cc..0000000 --- a/tuplities-pop-front/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-pop-front" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for popping elements from the front of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - -[dependencies] -tuplities-derive.workspace = true -tuplities-push-front.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-pop-front/src/lib.rs b/tuplities-pop-front/src/lib.rs deleted file mode 100644 index 9a28297..0000000 --- a/tuplities-pop-front/src/lib.rs +++ /dev/null @@ -1,96 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TuplePopFront` trait. -use tuplities_push_front::TuplePushFront; - -#[tuplities_derive::impl_pop_front] -/// A trait for tuples that defines the `Front` and `Tail` types, and provides a method to pop the front element. -pub trait TuplePopFront { - /// The type of the first element. - type Front; - - /// The type of the tuple after removing the first element. - type Tail: TuplePushFront; - - /// Consumes the tuple and returns the first element and the remaining tuple. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_pop_front::TuplePopFront; - /// - /// let tuple = (1, 2, 3); - /// let (first, rest) = tuple.pop_front(); - /// assert_eq!(first, 1); - /// assert_eq!(rest, (2, 3)); - /// ``` - /// - /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. - fn pop_front(self) -> (Self::Front, Self::Tail); -} - -/// A trait for accessing a reference to the first element of a tuple. -/// -/// This trait provides a method to get a reference to the first element without consuming the tuple. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_pop_front::TupleRefFront; -/// -/// let tuple = (1, 2, 3); -/// let first = tuple.ref_front(); -/// assert_eq!(*first, 1); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleRefFront: TuplePopFront { - /// Returns a reference to the first element of the tuple. - fn ref_front(&self) -> &Self::Front; -} - -/// A trait for accessing a mutable reference to the first element of a tuple. -/// -/// This trait provides a method to get a mutable reference to the first element without consuming the tuple. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_pop_front::TupleMutFront; -/// -/// let mut tuple = (1, 2, 3); -/// let first = tuple.mut_front(); -/// *first = 42; -/// assert_eq!(tuple, (42, 2, 3)); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleMutFront: TupleRefFront { - /// Returns a mutable reference to the first element of the tuple. - fn mut_front(&mut self) -> &mut Self::Front; -} - -#[cfg(test)] -mod tests { - use super::TuplePopFront; - - #[test] - fn test_pop_front_single_element_tuple() { - let tuple = (42,); - let (front, _tail): (i32, ()) = tuple.pop_front(); - // Check that the types are correct: (T,) -> Front = T, Tail = () - let expected_front: i32 = 42; - assert_eq!(front, expected_front); - } - - #[test] - fn test_pop_front_two_element_tuple() { - let tuple = (1, 2); - let (front, tail) = tuple.pop_front(); - // Check that the types are correct: (T1, T2) -> Front = T1, Tail = (T2,) - let expected_front: i32 = 1; - let expected_tail: (i32,) = (2,); - assert_eq!(front, expected_front); - assert_eq!(tail, expected_tail); - } -} diff --git a/tuplities-push-back/Cargo.toml b/tuplities-push-back/Cargo.toml deleted file mode 100644 index 37898f9..0000000 --- a/tuplities-push-back/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-push-back" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for pushing elements to the back of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-push-back/src/lib.rs b/tuplities-push-back/src/lib.rs deleted file mode 100644 index 43d26db..0000000 --- a/tuplities-push-back/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TuplePushBack` trait. - -#[tuplities_derive::impl_push_back] -/// A trait for tuples that allows pushing an element to the back. -pub trait TuplePushBack { - /// The type of the tuple after adding `T` to the back. - type Output; - - /// Consumes the tuple and appends the given value, returning the new tuple. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_push_back::TuplePushBack; - /// - /// let tuple = ("hello",); - /// let new_tuple = tuple.push_back("world"); - /// assert_eq!(new_tuple, ("hello", "world")); - /// ``` - /// - /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. - fn push_back(self, value: T) -> Self::Output; -} - -#[cfg(test)] -mod tests { - use super::TuplePushBack; - - #[test] - fn test_push_back_zero_sized_tuple() { - let tuple = (); - let result = tuple.push_back(42); - // Check that the type is correct: () + i32 = (i32,) - let expected: (i32,) = (42,); - assert_eq!(result, expected); - } -} diff --git a/tuplities-push-front/Cargo.toml b/tuplities-push-front/Cargo.toml deleted file mode 100644 index b14367c..0000000 --- a/tuplities-push-front/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "tuplities-push-front" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for pushing elements to the front of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-push-front/src/lib.rs b/tuplities-push-front/src/lib.rs deleted file mode 100644 index 68a0986..0000000 --- a/tuplities-push-front/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TuplePushFront` trait. - -#[tuplities_derive::impl_push_front] -/// A trait for tuples that allows pushing an element to the front. -pub trait TuplePushFront { - /// The type of the tuple after adding `T` to the front. - type Output; - - /// Consumes the tuple and prepends the given value, returning the new tuple. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_push_front::TuplePushFront; - /// - /// let tuple = ("world",); - /// let new_tuple = tuple.push_front("hello"); - /// assert_eq!(new_tuple, ("hello", "world")); - /// ``` - /// - /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. - fn push_front(self, value: T) -> Self::Output; -} - -#[cfg(test)] -mod tests { - use super::TuplePushFront; - - #[test] - fn test_push_front_zero_sized_tuple() { - let tuple: () = (); - let result: (i32,) = tuple.push_front(42); - // Check that the type is correct: i32 + () = (i32,) - let expected: (i32,) = (42,); - assert_eq!(result, expected); - } -} diff --git a/tuplities-ref/Cargo.toml b/tuplities-ref/Cargo.toml deleted file mode 100644 index b85ddfd..0000000 --- a/tuplities-ref/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-ref" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for getting tuples of references from tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-ref/src/lib.rs b/tuplities-ref/src/lib.rs deleted file mode 100644 index 755ee11..0000000 --- a/tuplities-ref/src/lib.rs +++ /dev/null @@ -1,68 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleRef` trait. - -#[tuplities_derive::impl_tuple_ref] -/// A trait for tuples that provides a method to get a tuple of references. -/// -/// This trait provides both an associated type `Ref<'a>` that represents a tuple -/// of references to the elements, and a method `tuple_ref` that returns such a tuple. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_ref::TupleRef; -/// -/// let tuple = (1, "hello".to_string(), vec![1, 2, 3]); -/// let refs = tuple.tuple_ref(); -/// assert_eq!(refs, (&1, &"hello".to_string(), &vec![1, 2, 3])); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleRef { - /// The type of a tuple containing references to each element. - type Ref<'a> - where - Self: 'a; - - /// Returns a tuple of references to each element. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_ref::TupleRef; - /// - /// let tuple = (42, "world"); - /// let refs = tuple.tuple_ref(); - /// assert_eq!(refs, (&42, &"world")); - /// ``` - fn tuple_ref(&self) -> Self::Ref<'_>; -} - -#[tuplities_derive::impl_tuple_ref_map] - -/// A trait for applying `TupleRef` to each element of a tuple. -/// -/// This trait takes a tuple where each element implements `TupleRef` and returns -/// a tuple where each element is the result of calling `tuple_ref()` on the original elements. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_ref::TupleRefMap; -/// -/// let matrix = ((1, 2), (3, 4), (5, 6)); -/// let ref_matrix = matrix.tuple_ref_map(); -/// assert_eq!(ref_matrix, ((&1, &2), (&3, &4), (&5, &6))); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleRefMap { - /// The type of a tuple containing tuples of references to each inner element. - type RefMap<'a> - where - Self: 'a; - - /// Returns a tuple where each element is a tuple of references to the inner elements. - fn tuple_ref_map(&self) -> Self::RefMap<'_>; -} diff --git a/tuplities-remove/Cargo.toml b/tuplities-remove/Cargo.toml deleted file mode 100644 index de9db40..0000000 --- a/tuplities-remove/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "tuplities-remove" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for removing elements from tuples at compile-time indices" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -typenum = { workspace = true } -tuplities-derive = { workspace = true } - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] - -[lints] -workspace = true diff --git a/tuplities-remove/src/lib.rs b/tuplities-remove/src/lib.rs deleted file mode 100644 index 3ce7d30..0000000 --- a/tuplities-remove/src/lib.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! A trait for removing elements at specific indices from tuples. -//! -//! This crate provides the `TupleRemove` trait, which allows removing an element -//! at a compile-time known index from a tuple, returning the element and the -//! remaining tuple. - -#![no_std] - -/// A trait for removing an element at a specific index from a tuple. -/// -/// This trait allows removing an element at compile-time known index `Idx` -/// from a tuple, returning the element and the remaining tuple. -/// -/// # Examples -/// -/// ``` -/// use tuplities_remove::TupleRemove; -/// use typenum::U1; -/// -/// let tuple = (1, "hello", 3.14); -/// let (removed, remainder) = TupleRemove::::remove(tuple); -/// assert_eq!(removed, "hello"); -/// assert_eq!(remainder, (1, 3.14)); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -#[tuplities_derive::impl_remove] -pub trait TupleRemove { - /// The type of the element being removed. - type Type; - - /// The type of the remaining tuple after removing. - type Remainder; - - /// Removes the element at index `Idx` from the tuple. - /// - /// Returns a tuple containing the removed element and the remaining tuple. - fn remove(self) -> (Self::Type, Self::Remainder); -} - -#[cfg(test)] -mod tests { - use super::TupleRemove; - use typenum::{U0, U1}; - - #[test] - fn test_remove_single_element_tuple() { - let tuple = (42,); - let (removed, _remainder): (i32, ()) = TupleRemove::::remove(tuple); - // Check types: (i32,) remove U0 -> Type = i32, Remainder = () - let expected_removed: i32 = 42; - assert_eq!(removed, expected_removed); - } - - #[test] - fn test_remove_two_element_tuple() { - // Remove at U0 - let tuple = (1, 2); - let (removed0, remainder0) = TupleRemove::::remove(tuple); - let expected_removed0: i32 = 1; - let expected_remainder0: (i32,) = (2,); - assert_eq!(removed0, expected_removed0); - assert_eq!(remainder0, expected_remainder0); - - // Remove at U1 - let tuple = (1, 2); - let (removed1, remainder1) = TupleRemove::::remove(tuple); - let expected_removed1: i32 = 2; - let expected_remainder1: (i32,) = (1,); - assert_eq!(removed1, expected_removed1); - assert_eq!(remainder1, expected_remainder1); - } -} diff --git a/tuplities-replicate/Cargo.toml b/tuplities-replicate/Cargo.toml deleted file mode 100644 index 82c451e..0000000 --- a/tuplities-replicate/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-replicate" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for replicating values into tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-replicate/src/lib.rs b/tuplities-replicate/src/lib.rs deleted file mode 100644 index 30da8ae..0000000 --- a/tuplities-replicate/src/lib.rs +++ /dev/null @@ -1,41 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleReplicate` trait. - -#[tuplities_derive::impl_tuple_replicate] -/// A trait for replicating a value into a tuple. -/// -/// This trait allows creating a tuple where all elements are the same value. -/// The implementation is optimized to avoid unnecessary cloning: for empty tuples -/// and single-element tuples, no `Clone` bound is required. For tuples with 2+ -/// elements, `Clone` is required but the original value is moved to the last -/// position to minimize clones. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleReplicate { - /// Creates a tuple where all elements are the provided value. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_replicate::TupleReplicate; - /// - /// let tuple: (i32, i32, i32) = TupleReplicate::tuple_replicate(42); - /// assert_eq!(tuple, (42, 42, 42)); - /// let _tuple_empty: () = TupleReplicate::tuple_replicate(42); - /// let tuple_single: (i32,) = TupleReplicate::tuple_replicate(42); - /// assert_eq!(tuple_single, (42,)); - /// let tuple_two: (i32, i32) = TupleReplicate::tuple_replicate(42); - /// assert_eq!(tuple_two, (42, 42)); - /// - /// let tuple_refs: (&str, &str, &str) = TupleReplicate::tuple_replicate("hello"); - /// assert_eq!(tuple_refs, ("hello", "hello", "hello")); - /// - /// let tuple_strings: (String, String) = TupleReplicate::tuple_replicate(String::from("world")); - /// assert_eq!(tuple_strings, (String::from("world"), String::from("world"))); - /// - /// let tuple_refs2: (&i32, &i32) = TupleReplicate::tuple_replicate(&100); - /// assert_eq!(tuple_refs2, (&100, &100)); - /// ``` - fn tuple_replicate(value: T) -> Self; -} diff --git a/tuplities-reverse/Cargo.toml b/tuplities-reverse/Cargo.toml deleted file mode 100644 index 55f159a..0000000 --- a/tuplities-reverse/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tuplities-reverse" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for reversing the order of tuple elements" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-reverse/src/lib.rs b/tuplities-reverse/src/lib.rs deleted file mode 100644 index a6dfb38..0000000 --- a/tuplities-reverse/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleReverse` trait. - -#[tuplities_derive::impl_reverse] -/// A trait for reversing the elements of a tuple. -/// -/// This trait provides a method to reverse the order of elements in a tuple. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_reverse::TupleReverse; -/// -/// let tuple = (1, "hello", 3.14); -/// let reversed = tuple.reverse(); -/// assert_eq!(reversed, (3.14, "hello", 1)); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleReverse { - /// The type of the tuple with elements in reverse order. - type Output; - - /// Consumes the tuple and returns a new tuple with elements in reverse order. - fn reverse(self) -> Self::Output; -} - -#[cfg(test)] -mod tests { - use super::TupleReverse; - - #[test] - fn test_reverse_empty_tuple() { - let tuple = (); - let _reversed: () = tuple.reverse(); - } - - #[test] - fn test_reverse_single_element() { - let tuple = (42,); - let reversed = tuple.reverse(); - assert_eq!(reversed, (42,)); - } - - #[test] - fn test_reverse_two_elements() { - let tuple = (1, 2); - let reversed = tuple.reverse(); - assert_eq!(reversed, (2, 1)); - } - - #[test] - fn test_reverse_three_elements() { - let tuple = (1, 2, 3); - let reversed = tuple.reverse(); - assert_eq!(reversed, (3, 2, 1)); - } -} diff --git a/tuplities-row/Cargo.toml b/tuplities-row/Cargo.toml deleted file mode 100644 index c38d949..0000000 --- a/tuplities-row/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -name = "tuplities-row" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for indexing rows in tuples of tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - -[dependencies] -tuplities-derive.workspace = true -tuplities-index.workspace = true -tuplities-ref.workspace = true -tuplities-mut.workspace = true -tuplities-len.workspace = true -tuplities-pop-front.workspace = true -tuplities-pop-back.workspace = true -typenum.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = [ - "tuplities-derive/size-16", - "tuplities-index/size-16", - "tuplities-ref/size-16", - "tuplities-mut/size-16", - "tuplities-len/size-16", -] -size-32 = [ - "tuplities-derive/size-32", - "tuplities-index/size-32", - "tuplities-ref/size-32", - "tuplities-mut/size-32", - "tuplities-len/size-32", -] -size-48 = [ - "tuplities-derive/size-48", - "tuplities-index/size-48", - "tuplities-ref/size-48", - "tuplities-mut/size-48", - "tuplities-len/size-48", -] -size-64 = [ - "tuplities-derive/size-64", - "tuplities-index/size-64", - "tuplities-ref/size-64", - "tuplities-mut/size-64", - "tuplities-len/size-64", -] -size-96 = [ - "tuplities-derive/size-96", - "tuplities-index/size-96", - "tuplities-ref/size-96", - "tuplities-mut/size-96", - "tuplities-len/size-96", -] -size-128 = [ - "tuplities-derive/size-128", - "tuplities-index/size-128", - "tuplities-ref/size-128", - "tuplities-mut/size-128", - "tuplities-len/size-128", -] diff --git a/tuplities-row/src/lib.rs b/tuplities-row/src/lib.rs deleted file mode 100644 index f37ecf9..0000000 --- a/tuplities-row/src/lib.rs +++ /dev/null @@ -1,113 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleRow` and `TupleRowMut` traits. - -use tuplities_len::TupleLen; -use tuplities_mut::TupleMut; -use tuplities_ref::TupleRef; - -/// A trait for indexing rows in tuples of tuples. -/// -/// This trait allows accessing elements at a specific index across all tuples in a tuple of tuples. -/// For a tuple of tuples like `((A, B), (C, D))`, `TupleRow` would return `(&A, &C)` and `TupleRow` would return `(&B, &D)`. -/// -/// Each inner tuple must implement `TupleIndex`, and the returned row tuple implements `TupleRef`. -/// -/// # Examples -/// -/// ``` -/// use tuplities_row::TupleRow; -/// use typenum::U0; -/// -/// let matrix = ((1, 2), (3, 4), (5, 6)); -/// let first_row = TupleRow::::tuple_row(&matrix); -/// assert_eq!(first_row, (&1, &3, &5)); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -#[tuplities_derive::impl_row] -pub trait TupleRow: TupleLen { - /// The type of the row tuple containing elements at index `Idx`. - type RowType: TupleRef + TupleLen::Len>; - - /// Returns a tuple of references to the elements at index `Idx` in each inner tuple. - fn tuple_row(&self) -> ::Ref<'_>; -} - -/// A trait for mutable indexing rows in tuples of tuples. -/// -/// This trait allows mutable access to elements at a specific index across all tuples in a tuple of tuples. -/// For a tuple of tuples like `((A, B), (C, D))`, `TupleRowMut` would return `(&mut A, &mut C)` and `TupleRowMut` would return `(&mut B, &mut D)`. -/// -/// Each inner tuple must implement `TupleIndexMut`, and the returned row tuple implements `TupleMut`. -/// -/// # Examples -/// -/// ``` -/// use tuplities_row::TupleRowMut; -/// use typenum::U0; -/// -/// let mut matrix = ((1, 2), (3, 4), (5, 6)); -/// let first_row = TupleRowMut::::tuple_row_mut(&mut matrix); -/// *first_row.0 = 10; -/// *first_row.1 = 30; -/// *first_row.2 = 50; -/// assert_eq!(matrix, ((10, 2), (30, 4), (50, 6))); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -#[tuplities_derive::impl_row_mut] -pub trait TupleRowMut: TupleRow { - /// Returns a tuple of mutable references to the elements at index `Idx` in each inner tuple. - fn tuple_row_mut(&mut self) -> ::Mut<'_>; -} - -/// A convenience trait for accessing the first row (index 0) in tuples of tuples. -/// -/// This trait is automatically implemented for any tuple of tuples where each inner tuple implements `TupleRefFront`. -/// -/// # Examples -/// -/// ``` -/// use tuplities_row::FirstTupleRow; -/// -/// let matrix = ((1, 2), (3, 4), (5, 6)); -/// let first_row = matrix.first_tuple_row(); -/// assert_eq!(first_row, (&1, &3, &5)); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -#[tuplities_derive::impl_first_tuple_row] -pub trait FirstTupleRow: TupleLen { - /// The type of the row tuple containing references to the first elements. - type FirstRowType: TupleRef + TupleLen::Len>; - - /// Returns a tuple of references to the first element in each inner tuple. - fn first_tuple_row(&self) -> ::Ref<'_>; -} - -/// A convenience trait for accessing the last row in tuples of tuples. -/// -/// This trait is automatically implemented for tuples of tuples where the implementation -/// depends on the length of the inner tuples. It provides access to the last element -/// of each inner tuple. -/// -/// # Examples -/// -/// ``` -/// use tuplities_row::LastTupleRow; -/// -/// let matrix = ((1, 2, 3), (4, 5, 6), (7, 8, 9)); -/// let last_row = matrix.last_tuple_row(); -/// assert_eq!(last_row, (&3, &6, &9)); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -#[tuplities_derive::impl_last_tuple_row] -pub trait LastTupleRow: TupleLen { - /// The type of the row tuple containing references to the last elements. - type LastRowType: TupleRef + TupleLen::Len>; - - /// Returns a tuple of references to the last element in each inner tuple. - fn last_tuple_row(&self) -> ::Ref<'_>; -} diff --git a/tuplities-split/Cargo.toml b/tuplities-split/Cargo.toml deleted file mode 100644 index a679832..0000000 --- a/tuplities-split/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "tuplities-split" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "A trait for splitting tuples at compile-time indices" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - - -[dependencies] -tuplities-derive.workspace = true -typenum.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-split/src/lib.rs b/tuplities-split/src/lib.rs deleted file mode 100644 index 92ef853..0000000 --- a/tuplities-split/src/lib.rs +++ /dev/null @@ -1,111 +0,0 @@ -#![no_std] - -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleSplit` trait. - -/// A trait for splitting a tuple at a compile-time known index. -/// -/// This trait allows splitting a tuple at a specific compile-time known index `Idx`, -/// returning two tuples: the elements before the index and the elements at and after the index. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_split::TupleSplit; -/// use typenum::U2; -/// -/// let tuple = (1, 2, 3, 4); -/// let (left, right) = TupleSplit::::split(tuple); -/// assert_eq!(left, (1, 2)); -/// assert_eq!(right, (3, 4)); -/// ``` -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -#[tuplities_derive::impl_split] -pub trait TupleSplit { - /// The type of the tuple containing elements before the split index. - type Left; - - /// The type of the tuple containing elements at and after the split index. - type Right; - - /// Splits the tuple at index `Idx`, returning two tuples. - fn split(self) -> (Self::Left, Self::Right); -} - -#[cfg(test)] -mod tests { - use super::TupleSplit; - use typenum::{U0, U1, U2, U3}; - - #[test] - fn test_split_at_zero() { - let tuple = (1, 2, 3); - let (left, right) = TupleSplit::::split(tuple); - assert_eq!(left, ()); - assert_eq!(right, (1, 2, 3)); - } - - #[test] - fn test_split_at_middle() { - let tuple = (1, 2, 3, 4); - let (left, right) = TupleSplit::::split(tuple); - assert_eq!(left, (1, 2)); - assert_eq!(right, (3, 4)); - } - - #[test] - fn test_split_at_end() { - let tuple = (1, 2, 3); - let (left, right) = TupleSplit::::split(tuple); - assert_eq!(left, (1, 2, 3)); - assert_eq!(right, ()); - } - - #[test] - fn test_split_empty_tuple() { - let tuple = (); - let (left, right) = TupleSplit::::split(tuple); - assert_eq!(left, ()); - assert_eq!(right, ()); - } - - #[test] - fn test_split_single_element_at_zero() { - let tuple = (42,); - let (left, right) = TupleSplit::::split(tuple); - assert_eq!(left, ()); - assert_eq!(right, (42,)); - } - - #[test] - fn test_split_single_element_at_one() { - let tuple = (42,); - let (left, right) = TupleSplit::::split(tuple); - assert_eq!(left, (42,)); - assert_eq!(right, ()); - } - - #[test] - fn test_split_two_elements_at_zero() { - let tuple = (1, 2); - let (left, right) = TupleSplit::::split(tuple); - assert_eq!(left, ()); - assert_eq!(right, (1, 2)); - } - - #[test] - fn test_split_two_elements_at_one() { - let tuple = (1, 2); - let (left, right) = TupleSplit::::split(tuple); - assert_eq!(left, (1,)); - assert_eq!(right, (2,)); - } - - #[test] - fn test_split_two_elements_at_two() { - let tuple = (1, 2); - let (left, right) = TupleSplit::::split(tuple); - assert_eq!(left, (1, 2)); - assert_eq!(right, ()); - } -} diff --git a/tuplities-try-from/Cargo.toml b/tuplities-try-from/Cargo.toml deleted file mode 100644 index 7e32b0f..0000000 --- a/tuplities-try-from/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "tuplities-try-from" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "Traits for fallible conversion of values into tuples" -readme.workspace = true -license-file.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true -homepage.workspace = true - -[dependencies] -tuplities-derive.workspace = true - -[lints] -workspace = true - -[features] -default = [] -size-16 = ["tuplities-derive/size-16"] -size-32 = ["tuplities-derive/size-32"] -size-48 = ["tuplities-derive/size-48"] -size-64 = ["tuplities-derive/size-64"] -size-96 = ["tuplities-derive/size-96"] -size-128 = ["tuplities-derive/size-128"] diff --git a/tuplities-try-from/src/lib.rs b/tuplities-try-from/src/lib.rs deleted file mode 100644 index 38424a4..0000000 --- a/tuplities-try-from/src/lib.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! [tuplities](https://github.com/lucacappelletti94/tuplities) suite crate providing the `TupleTryFrom` and `TupleTryInto` traits. - -#[tuplities_derive::impl_tuple_try_from] -/// A trait for fallibly converting from one tuple type to another. -/// -/// This trait allows converting between tuples where each element can be converted -/// using the standard `TryFrom` trait. The error type `E` must implement `From` for -/// each individual `TryFrom` error in the tuple, allowing error accumulation. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleTryFrom { - /// Attempts to convert from `T` into `Self`. - /// - /// # Errors - /// - /// Returns an error of type `E` if any element conversion fails. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_try_from::TupleTryFrom; - /// - /// let source = (1u32, 2u32); - /// let target: Result<(u8, u8), _> = TupleTryFrom::<_, std::num::TryFromIntError>::tuple_try_from(source); - /// assert_eq!(target, Ok((1, 2))); - /// - /// let invalid = (300u32, 2u32); // 300 is too big for u8 - /// let result: Result<(u8, u8), _> = TupleTryFrom::<_, std::num::TryFromIntError>::tuple_try_from(invalid); - /// assert!(result.is_err()); - /// ``` - fn tuple_try_from(value: T) -> Result - where - Self: Sized; -} - -/// A trait for fallibly converting a tuple into another tuple type. -/// -/// This trait allows converting tuples into other tuple types where each element can be converted -/// using the standard `TryFrom` trait. The error type `E` must implement `From` for -/// each individual `TryFrom` error in the tuple, allowing error accumulation. -/// -/// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait TupleTryInto { - /// Attempts to convert `self` into `T`. - /// - /// # Errors - /// - /// Returns an error of type `E` if any element conversion fails. - /// - /// # Examples - /// - /// ```rust - /// use tuplities_try_from::TupleTryInto; - /// - /// let source = (1u32, 2u32); - /// let target: Result<(u8, u8), _> = TupleTryInto::<(u8, u8), std::num::TryFromIntError>::tuple_try_into(source); - /// assert_eq!(target, Ok((1, 2))); - /// - /// let invalid = (300u32, 2u32); // 300 is too big for u8 - /// let result: Result<(u8, u8), _> = TupleTryInto::<(u8, u8), std::num::TryFromIntError>::tuple_try_into(invalid); - /// assert!(result.is_err()); - /// ``` - fn tuple_try_into(self) -> Result; -} - -// Blanket implementation of TupleTryInto based on TupleTryFrom -impl TupleTryInto for U -where - T: TupleTryFrom, -{ - fn tuple_try_into(self) -> Result { - T::tuple_try_from(self) - } -} diff --git a/tuplities/Cargo.toml b/tuplities/Cargo.toml index a280333..d523922 100644 --- a/tuplities/Cargo.toml +++ b/tuplities/Cargo.toml @@ -4,274 +4,34 @@ version.workspace = true edition.workspace = true authors.workspace = true description.workspace = true -readme.workspace = true -license-file.workspace = true +readme = "README.md" +license.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true -homepage.workspace = true - +rust-version.workspace = true [dependencies] -tuplities-clone = { workspace = true, optional = true } -tuplities-copy = { workspace = true, optional = true } -tuplities-debug = { workspace = true, optional = true } -tuplities-default = { workspace = true, optional = true } -tuplities-eq = { workspace = true, optional = true } -tuplities-flatten-nest = { workspace = true, optional = true } -tuplities-from = { workspace = true, optional = true } -tuplities-hash = { workspace = true, optional = true } -tuplities-mut = { workspace = true, optional = true } -tuplities-option = { workspace = true, optional = true } -tuplities-ord = { workspace = true, optional = true } -tuplities-partial-eq = { workspace = true, optional = true } -tuplities-partial-ord = { workspace = true, optional = true } -tuplities-remove = { workspace = true, optional = true } -tuplities-insert = { workspace = true, optional = true } -tuplities-len = { workspace = true, optional = true } -tuplities-index = { workspace = true, optional = true } -tuplities-pop-front = { workspace = true, optional = true } -tuplities-pop-back = { workspace = true, optional = true } -tuplities-push-front = { workspace = true, optional = true } -tuplities-push-back = { workspace = true, optional = true } -tuplities-ref = { workspace = true, optional = true } -tuplities-replicate = { workspace = true, optional = true } -tuplities-reverse = { workspace = true, optional = true } -tuplities-row = { workspace = true, optional = true } -tuplities-split = { workspace = true, optional = true } -tuplities-try-from = { workspace = true, optional = true } +typenum.workspace = true [lints] workspace = true [features] -default = [ - "clone", - "copy", - "debug", - "tuple-default", - "eq", - "flatten-nest", - "from", - "hash", - "mut", - "option", - "ord", - "partial-eq", - "partial-ord", - "remove", - "insert", - "len", - "index", - "pop-front", - "pop-back", - "push-front", - "push-back", - "ref", - "replicate", - "reverse", - "row", - "split", - "try-from", -] -clone = ["tuplities-clone"] -copy = ["tuplities-copy"] -debug = ["tuplities-debug"] -tuple-default = ["tuplities-default"] -eq = ["tuplities-eq"] -flatten-nest = ["tuplities-flatten-nest"] -from = ["tuplities-from"] -hash = ["tuplities-hash"] -mut = ["tuplities-mut"] -option = ["tuplities-option"] -ord = ["tuplities-ord"] -partial-eq = ["tuplities-partial-eq"] -partial-ord = ["tuplities-partial-ord"] -remove = ["tuplities-remove"] -insert = ["tuplities-insert"] -len = ["tuplities-len"] -index = ["tuplities-index"] -pop-front = ["tuplities-pop-front"] -pop-back = ["tuplities-pop-back"] -push-front = ["tuplities-push-front"] -push-back = ["tuplities-push-back"] -ref = ["tuplities-ref"] -replicate = ["tuplities-replicate"] -reverse = ["tuplities-reverse"] -row = ["tuplities-row"] -split = ["tuplities-split"] -try-from = ["tuplities-try-from"] -size-16 = [ - "tuplities-clone/size-16", - "tuplities-copy/size-16", - "tuplities-debug/size-16", - "tuplities-default/size-16", - "tuplities-eq/size-16", - "tuplities-flatten-nest/size-16", - "tuplities-from/size-16", - "tuplities-hash/size-16", - "tuplities-mut/size-16", - "tuplities-option/size-16", - "tuplities-ord/size-16", - "tuplities-partial-eq/size-16", - "tuplities-partial-ord/size-16", - "tuplities-remove/size-16", - "tuplities-insert/size-16", - "tuplities-len/size-16", - "tuplities-index/size-16", - "tuplities-pop-front/size-16", - "tuplities-pop-back/size-16", - "tuplities-push-front/size-16", - "tuplities-push-back/size-16", - "tuplities-ref/size-16", - "tuplities-replicate/size-16", - "tuplities-reverse/size-16", - "tuplities-row/size-16", - "tuplities-split/size-16", - "tuplities-try-from/size-16", -] -size-32 = [ - "tuplities-clone/size-32", - "tuplities-copy/size-32", - "tuplities-debug/size-32", - "tuplities-default/size-32", - "tuplities-eq/size-32", - "tuplities-flatten-nest/size-32", - "tuplities-from/size-32", - "tuplities-hash/size-32", - "tuplities-mut/size-32", - "tuplities-option/size-32", - "tuplities-ord/size-32", - "tuplities-partial-eq/size-32", - "tuplities-partial-ord/size-32", - "tuplities-remove/size-32", - "tuplities-insert/size-32", - "tuplities-len/size-32", - "tuplities-index/size-32", - "tuplities-pop-front/size-32", - "tuplities-pop-back/size-32", - "tuplities-push-front/size-32", - "tuplities-push-back/size-32", - "tuplities-ref/size-32", - "tuplities-replicate/size-32", - "tuplities-reverse/size-32", - "tuplities-row/size-32", - "tuplities-split/size-32", - "tuplities-try-from/size-32", -] -size-48 = [ - "tuplities-clone/size-48", - "tuplities-copy/size-48", - "tuplities-debug/size-48", - "tuplities-default/size-48", - "tuplities-eq/size-48", - "tuplities-flatten-nest/size-48", - "tuplities-from/size-48", - "tuplities-hash/size-48", - "tuplities-mut/size-48", - "tuplities-option/size-48", - "tuplities-ord/size-48", - "tuplities-partial-eq/size-48", - "tuplities-partial-ord/size-48", - "tuplities-remove/size-48", - "tuplities-insert/size-48", - "tuplities-len/size-48", - "tuplities-index/size-48", - "tuplities-pop-front/size-48", - "tuplities-pop-back/size-48", - "tuplities-push-front/size-48", - "tuplities-push-back/size-48", - "tuplities-ref/size-48", - "tuplities-replicate/size-48", - "tuplities-reverse/size-48", - "tuplities-row/size-48", - "tuplities-split/size-48", - "tuplities-try-from/size-48", -] -size-64 = [ - "tuplities-clone/size-64", - "tuplities-copy/size-64", - "tuplities-debug/size-64", - "tuplities-default/size-64", - "tuplities-eq/size-64", - "tuplities-flatten-nest/size-64", - "tuplities-from/size-64", - "tuplities-hash/size-64", - "tuplities-mut/size-64", - "tuplities-option/size-64", - "tuplities-ord/size-64", - "tuplities-partial-eq/size-64", - "tuplities-partial-ord/size-64", - "tuplities-remove/size-64", - "tuplities-insert/size-64", - "tuplities-len/size-64", - "tuplities-index/size-64", - "tuplities-pop-front/size-64", - "tuplities-pop-back/size-64", - "tuplities-push-front/size-64", - "tuplities-push-back/size-64", - "tuplities-ref/size-64", - "tuplities-replicate/size-64", - "tuplities-reverse/size-64", - "tuplities-row/size-64", - "tuplities-split/size-64", - "tuplities-try-from/size-64", -] -size-96 = [ - "tuplities-clone/size-96", - "tuplities-copy/size-96", - "tuplities-debug/size-96", - "tuplities-default/size-96", - "tuplities-eq/size-96", - "tuplities-flatten-nest/size-96", - "tuplities-from/size-96", - "tuplities-hash/size-96", - "tuplities-mut/size-96", - "tuplities-option/size-96", - "tuplities-ord/size-96", - "tuplities-partial-eq/size-96", - "tuplities-partial-ord/size-96", - "tuplities-remove/size-96", - "tuplities-insert/size-96", - "tuplities-len/size-96", - "tuplities-index/size-96", - "tuplities-pop-front/size-96", - "tuplities-pop-back/size-96", - "tuplities-push-front/size-96", - "tuplities-push-back/size-96", - "tuplities-ref/size-96", - "tuplities-replicate/size-96", - "tuplities-reverse/size-96", - "tuplities-row/size-96", - "tuplities-split/size-96", - "tuplities-try-from/size-96", -] -size-128 = [ - "tuplities-clone/size-128", - "tuplities-copy/size-128", - "tuplities-debug/size-128", - "tuplities-default/size-128", - "tuplities-eq/size-128", - "tuplities-flatten-nest/size-128", - "tuplities-from/size-128", - "tuplities-hash/size-128", - "tuplities-mut/size-128", - "tuplities-option/size-128", - "tuplities-ord/size-128", - "tuplities-partial-eq/size-128", - "tuplities-partial-ord/size-128", - "tuplities-remove/size-128", - "tuplities-insert/size-128", - "tuplities-len/size-128", - "tuplities-index/size-128", - "tuplities-pop-front/size-128", - "tuplities-pop-back/size-128", - "tuplities-push-front/size-128", - "tuplities-push-back/size-128", - "tuplities-ref/size-128", - "tuplities-replicate/size-128", - "tuplities-reverse/size-128", - "tuplities-row/size-128", - "tuplities-split/size-128", - "tuplities-try-from/size-128", -] +default = ["flatten-nest"] +alloc = [] +flatten-nest = ["alloc"] +size-16 = [] +size-32 = [] +size-48 = [] +size-64 = [] +size-96 = [] +size-128 = [] + +[[test]] +name = "flat_bridge_contracts" +required-features = ["flatten-nest"] + +[[test]] +name = "flat_bridge_sizes" +required-features = ["flatten-nest"] diff --git a/tuplities/LICENSE b/tuplities/LICENSE new file mode 120000 index 0000000..ea5b606 --- /dev/null +++ b/tuplities/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/tuplities/src/flat_bridge.rs b/tuplities/src/flat_bridge.rs new file mode 100644 index 0000000..fff8c6d --- /dev/null +++ b/tuplities/src/flat_bridge.rs @@ -0,0 +1,134 @@ +//! Flat tuple conversions up to the selected arity. + +use crate::{FlattenNestedTuple, NestTuple, NestTupleMut, NestTupleRef}; + +macro_rules! nested_type { + ($head:ty) => { ($head,) }; + ($head:ty, $($tail:ty),+) => { ($head, nested_type!($($tail),+)) }; +} + +macro_rules! nested_value { + ($head:expr) => { ($head,) }; + ($head:expr, $($tail:expr),+) => { ($head, nested_value!($($tail),+)) }; +} + +macro_rules! nested_pattern { + ($head:ident) => { ($head,) }; + ($head:ident, $($tail:ident),+) => { ($head, nested_pattern!($($tail),+)) }; +} + +macro_rules! flat_bridge { + ($($ty:ident $value:ident $index:tt),+) => { + impl<$($ty),+> NestTuple for ($($ty,)+) { + type Nested = nested_type!($($ty),+); + + #[inline] + fn nest(self) -> Self::Nested { + nested_value!($(self.$index),+) + } + } + + impl<$($ty),+> NestTupleRef for ($($ty,)+) { + type NestedRef<'a> = nested_type!($(&'a $ty),+) where Self: 'a; + + #[inline] + fn nest_ref(&self) -> Self::NestedRef<'_> { + nested_value!($(&self.$index),+) + } + } + + impl<$($ty),+> NestTupleMut for ($($ty,)+) { + type NestedMut<'a> = nested_type!($(&'a mut $ty),+) where Self: 'a; + + #[inline] + fn nest_mut(&mut self) -> Self::NestedMut<'_> { + nested_value!($(&mut self.$index),+) + } + } + + impl<$($ty),+> FlattenNestedTuple for nested_type!($($ty),+) { + type Flattened = ($($ty,)+); + + #[inline] + fn flatten(self) -> Self::Flattened { + let nested_pattern!($($value),+) = self; + ($($value,)+) + } + } + }; +} + +macro_rules! bridge_prefixes { + (($($ty:ident $value:ident $index:tt),*);) => {}; + (($($ty:ident $value:ident $index:tt),*); + $next_ty:ident $next_value:ident $next_index:tt + $(, $rest_ty:ident $rest_value:ident $rest_index:tt)*) => { + flat_bridge!($($ty $value $index,)* $next_ty $next_value $next_index); + bridge_prefixes!( + ($($ty $value $index,)* $next_ty $next_value $next_index); + $($rest_ty $rest_value $rest_index),* + ); + }; +} + +bridge_prefixes!((T0 v0 0); T1 v1 1, T2 v2 2, T3 v3 3, T4 v4 4, T5 v5 5, T6 v6 6, T7 v7 7); + +#[cfg(any( + feature = "size-16", + feature = "size-32", + feature = "size-48", + feature = "size-64", + feature = "size-96", + feature = "size-128" +))] +mod width_16 { + use super::{FlattenNestedTuple, NestTuple, NestTupleMut, NestTupleRef}; + + bridge_prefixes!((T0 v0 0, T1 v1 1, T2 v2 2, T3 v3 3, T4 v4 4, T5 v5 5, T6 v6 6, T7 v7 7); T8 v8 8, T9 v9 9, T10 v10 10, T11 v11 11, T12 v12 12, T13 v13 13, T14 v14 14, T15 v15 15); +} + +#[cfg(any( + feature = "size-32", + feature = "size-48", + feature = "size-64", + feature = "size-96", + feature = "size-128" +))] +mod width_32 { + use super::{FlattenNestedTuple, NestTuple, NestTupleMut, NestTupleRef}; + + bridge_prefixes!((T0 v0 0, T1 v1 1, T2 v2 2, T3 v3 3, T4 v4 4, T5 v5 5, T6 v6 6, T7 v7 7, T8 v8 8, T9 v9 9, T10 v10 10, T11 v11 11, T12 v12 12, T13 v13 13, T14 v14 14, T15 v15 15); T16 v16 16, T17 v17 17, T18 v18 18, T19 v19 19, T20 v20 20, T21 v21 21, T22 v22 22, T23 v23 23, T24 v24 24, T25 v25 25, T26 v26 26, T27 v27 27, T28 v28 28, T29 v29 29, T30 v30 30, T31 v31 31); +} + +#[cfg(any( + feature = "size-48", + feature = "size-64", + feature = "size-96", + feature = "size-128" +))] +mod width_48 { + use super::{FlattenNestedTuple, NestTuple, NestTupleMut, NestTupleRef}; + + bridge_prefixes!((T0 v0 0, T1 v1 1, T2 v2 2, T3 v3 3, T4 v4 4, T5 v5 5, T6 v6 6, T7 v7 7, T8 v8 8, T9 v9 9, T10 v10 10, T11 v11 11, T12 v12 12, T13 v13 13, T14 v14 14, T15 v15 15, T16 v16 16, T17 v17 17, T18 v18 18, T19 v19 19, T20 v20 20, T21 v21 21, T22 v22 22, T23 v23 23, T24 v24 24, T25 v25 25, T26 v26 26, T27 v27 27, T28 v28 28, T29 v29 29, T30 v30 30, T31 v31 31); T32 v32 32, T33 v33 33, T34 v34 34, T35 v35 35, T36 v36 36, T37 v37 37, T38 v38 38, T39 v39 39, T40 v40 40, T41 v41 41, T42 v42 42, T43 v43 43, T44 v44 44, T45 v45 45, T46 v46 46, T47 v47 47); +} + +#[cfg(any(feature = "size-64", feature = "size-96", feature = "size-128"))] +mod width_64 { + use super::{FlattenNestedTuple, NestTuple, NestTupleMut, NestTupleRef}; + + bridge_prefixes!((T0 v0 0, T1 v1 1, T2 v2 2, T3 v3 3, T4 v4 4, T5 v5 5, T6 v6 6, T7 v7 7, T8 v8 8, T9 v9 9, T10 v10 10, T11 v11 11, T12 v12 12, T13 v13 13, T14 v14 14, T15 v15 15, T16 v16 16, T17 v17 17, T18 v18 18, T19 v19 19, T20 v20 20, T21 v21 21, T22 v22 22, T23 v23 23, T24 v24 24, T25 v25 25, T26 v26 26, T27 v27 27, T28 v28 28, T29 v29 29, T30 v30 30, T31 v31 31, T32 v32 32, T33 v33 33, T34 v34 34, T35 v35 35, T36 v36 36, T37 v37 37, T38 v38 38, T39 v39 39, T40 v40 40, T41 v41 41, T42 v42 42, T43 v43 43, T44 v44 44, T45 v45 45, T46 v46 46, T47 v47 47); T48 v48 48, T49 v49 49, T50 v50 50, T51 v51 51, T52 v52 52, T53 v53 53, T54 v54 54, T55 v55 55, T56 v56 56, T57 v57 57, T58 v58 58, T59 v59 59, T60 v60 60, T61 v61 61, T62 v62 62, T63 v63 63); +} + +#[cfg(any(feature = "size-96", feature = "size-128"))] +mod width_96 { + use super::{FlattenNestedTuple, NestTuple, NestTupleMut, NestTupleRef}; + + bridge_prefixes!((T0 v0 0, T1 v1 1, T2 v2 2, T3 v3 3, T4 v4 4, T5 v5 5, T6 v6 6, T7 v7 7, T8 v8 8, T9 v9 9, T10 v10 10, T11 v11 11, T12 v12 12, T13 v13 13, T14 v14 14, T15 v15 15, T16 v16 16, T17 v17 17, T18 v18 18, T19 v19 19, T20 v20 20, T21 v21 21, T22 v22 22, T23 v23 23, T24 v24 24, T25 v25 25, T26 v26 26, T27 v27 27, T28 v28 28, T29 v29 29, T30 v30 30, T31 v31 31, T32 v32 32, T33 v33 33, T34 v34 34, T35 v35 35, T36 v36 36, T37 v37 37, T38 v38 38, T39 v39 39, T40 v40 40, T41 v41 41, T42 v42 42, T43 v43 43, T44 v44 44, T45 v45 45, T46 v46 46, T47 v47 47, T48 v48 48, T49 v49 49, T50 v50 50, T51 v51 51, T52 v52 52, T53 v53 53, T54 v54 54, T55 v55 55, T56 v56 56, T57 v57 57, T58 v58 58, T59 v59 59, T60 v60 60, T61 v61 61, T62 v62 62, T63 v63 63); T64 v64 64, T65 v65 65, T66 v66 66, T67 v67 67, T68 v68 68, T69 v69 69, T70 v70 70, T71 v71 71, T72 v72 72, T73 v73 73, T74 v74 74, T75 v75 75, T76 v76 76, T77 v77 77, T78 v78 78, T79 v79 79, T80 v80 80, T81 v81 81, T82 v82 82, T83 v83 83, T84 v84 84, T85 v85 85, T86 v86 86, T87 v87 87, T88 v88 88, T89 v89 89, T90 v90 90, T91 v91 91, T92 v92 92, T93 v93 93, T94 v94 94, T95 v95 95); +} + +#[cfg(feature = "size-128")] +mod width_128 { + use super::{FlattenNestedTuple, NestTuple, NestTupleMut, NestTupleRef}; + + bridge_prefixes!((T0 v0 0, T1 v1 1, T2 v2 2, T3 v3 3, T4 v4 4, T5 v5 5, T6 v6 6, T7 v7 7, T8 v8 8, T9 v9 9, T10 v10 10, T11 v11 11, T12 v12 12, T13 v13 13, T14 v14 14, T15 v15 15, T16 v16 16, T17 v17 17, T18 v18 18, T19 v19 19, T20 v20 20, T21 v21 21, T22 v22 22, T23 v23 23, T24 v24 24, T25 v25 25, T26 v26 26, T27 v27 27, T28 v28 28, T29 v29 29, T30 v30 30, T31 v31 31, T32 v32 32, T33 v33 33, T34 v34 34, T35 v35 35, T36 v36 36, T37 v37 37, T38 v38 38, T39 v39 39, T40 v40 40, T41 v41 41, T42 v42 42, T43 v43 43, T44 v44 44, T45 v45 45, T46 v46 46, T47 v47 47, T48 v48 48, T49 v49 49, T50 v50 50, T51 v51 51, T52 v52 52, T53 v53 53, T54 v54 54, T55 v55 55, T56 v56 56, T57 v57 57, T58 v58 58, T59 v59 59, T60 v60 60, T61 v61 61, T62 v62 62, T63 v63 63, T64 v64 64, T65 v65 65, T66 v66 66, T67 v67 67, T68 v68 68, T69 v69 69, T70 v70 70, T71 v71 71, T72 v72 72, T73 v73 73, T74 v74 74, T75 v75 75, T76 v76 76, T77 v77 77, T78 v78 78, T79 v79 79, T80 v80 80, T81 v81 81, T82 v82 82, T83 v83 83, T84 v84 84, T85 v85 85, T86 v86 86, T87 v87 87, T88 v88 88, T89 v89 89, T90 v90 90, T91 v91 91, T92 v92 92, T93 v93 93, T94 v94 94, T95 v95 95); T96 v96 96, T97 v97 97, T98 v98 98, T99 v99 99, T100 v100 100, T101 v101 101, T102 v102 102, T103 v103 103, T104 v104 104, T105 v105 105, T106 v106 106, T107 v107 107, T108 v108 108, T109 v109 109, T110 v110 110, T111 v111 111, T112 v112 112, T113 v113 113, T114 v114 114, T115 v115 115, T116 v116 116, T117 v117 117, T118 v118 118, T119 v119 119, T120 v120 120, T121 v121 121, T122 v122 122, T123 v123 123, T124 v124 124, T125 v125 125, T126 v126 126, T127 v127 127); +} diff --git a/tuplities-flatten-nest/src/flatten_nested.rs b/tuplities/src/flatten_nested.rs similarity index 70% rename from tuplities-flatten-nest/src/flatten_nested.rs rename to tuplities/src/flatten_nested.rs index 4f49927..f56c4cc 100644 --- a/tuplities-flatten-nest/src/flatten_nested.rs +++ b/tuplities/src/flatten_nested.rs @@ -1,8 +1,6 @@ //! Module providing the `FlattenNestedTuple` trait for flattening nested tuples. -use tuplities_push_front::TuplePushFront; - -use crate::NestTuple; +use crate::nest::NestTuple; /// A trait for flattening nested tuples into flat tuples. /// @@ -15,6 +13,19 @@ pub trait FlattenNestedTuple { type Flattened: NestTuple; /// Flattens the nested tuple into a flat tuple. + /// + /// # Examples + /// + /// ``` + /// use tuplities::FlattenNestedTuple; + /// + /// assert_eq!((7_u8,).flatten(), (7_u8,)); + /// + /// # #[cfg(feature = "flatten-nest")] { + /// let nested = (1, (2, (3,))); + /// assert_eq!(nested.flatten(), (1, 2, 3)); + /// # } + /// ``` fn flatten(self) -> Self::Flattened; } @@ -35,20 +46,7 @@ impl FlattenNestedTuple for (N1,) { } } -impl FlattenNestedTuple for (Head, Tail) -where - Tail: FlattenNestedTuple>>, -{ - type Flattened = >::Output; - - #[inline] - fn flatten(self) -> Self::Flattened { - let (head, tail) = self; - tail.flatten().push_front(head) - } -} - -#[cfg(test)] +#[cfg(all(test, feature = "flatten-nest"))] mod tests { use super::*; diff --git a/tuplities/src/lib.rs b/tuplities/src/lib.rs index 2612eba..787b99c 100644 --- a/tuplities/src/lib.rs +++ b/tuplities/src/lib.rs @@ -1,74 +1,80 @@ #![no_std] +#![recursion_limit = "256"] #![doc = include_str!("../README.md")] -/// Re-exports of commonly used traits and types. +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "flatten-nest")] +mod flat_bridge; +mod flatten_nested; +#[cfg(feature = "flatten-nest")] +mod matrix; +mod nest; +mod nested_chain; +mod nested_index; +#[cfg(feature = "alloc")] +mod nested_into_vec; +mod nested_option; +mod nested_option_try_from; +mod nested_push_pop; +mod nested_ref; +mod nested_replicate; +mod nested_reverse; +mod nested_row; +mod nested_starts_with; +mod nested_structure; +mod nested_try_from; +mod nested_tuple; + +pub use flatten_nested::FlattenNestedTuple; +#[cfg(feature = "flatten-nest")] +pub use matrix::{ + FlattenMatrixElements, FlattenNestedTupleMatrix, NestMatrixElements, NestTupleMatrix, +}; +pub use nest::{NestTuple, NestTupleMut, NestTupleRef}; +pub use nested_chain::NestedTupleChain; +pub use nested_index::{NestedTupleIndex, NestedTupleIndexMut}; +#[cfg(feature = "alloc")] +pub use nested_into_vec::NestedTupleIntoVec; +pub use nested_option::{ + IntoNestedTupleOption, NestedTupleFlattenOption, NestedTupleOption, NestedTupleOptionWith, +}; +pub use nested_option_try_from::{ + NestedTupleOptionFrom, NestedTupleOptionInto, NestedTupleOptionTryFrom, + NestedTupleOptionTryInto, +}; +pub use nested_push_pop::{ + NestedTuplePopBack, NestedTuplePopFront, NestedTuplePushBack, NestedTuplePushFront, +}; +pub use nested_ref::{NestedTupleMut, NestedTupleRef}; +pub use nested_replicate::NestedTupleReplicate; +pub use nested_reverse::NestedTupleReverse; +pub use nested_row::{NestedTupleRow, NestedTupleRowMut}; +pub use nested_starts_with::NestedTupleStartsWith; +pub use nested_structure::{NestedTupleInsert, NestedTupleRemove, NestedTupleSplit}; +pub use nested_try_from::{ + NestedTupleFrom, NestedTupleInto, NestedTupleTryFrom, NestedTupleTryInto, +}; +pub use nested_tuple::NestedTuple; + +/// Traits and construction macros for recursive tuples and optional flat-tuple conversions. pub mod prelude { - #[cfg(feature = "clone")] - pub use tuplities_clone::TupleClone; - #[cfg(feature = "copy")] - pub use tuplities_copy::TupleCopy; - #[cfg(feature = "debug")] - pub use tuplities_debug::TupleDebug; - #[cfg(feature = "tuple-default")] - pub use tuplities_default::TupleDefault; - #[cfg(feature = "eq")] - pub use tuplities_eq::TupleEq; + #[cfg(feature = "alloc")] + pub use crate::NestedTupleIntoVec; #[cfg(feature = "flatten-nest")] - pub use tuplities_flatten_nest::{ - FlattenMatrixElements, FlattenNestedTuple, FlattenNestedTupleMatrix, IntoNestedTupleOption, - NestMatrixElements, NestTuple, NestTupleMatrix, NestTupleMut, NestTupleRef, - NestedTupleChain, NestedTupleFlattenOption, NestedTupleFrom, NestedTupleIndex, - NestedTupleIndexMut, NestedTupleInto, NestedTupleIntoVec, NestedTupleMut, - NestedTupleOption, NestedTupleOptionFrom, NestedTupleOptionInto, NestedTupleOptionTryFrom, + pub use crate::{ + FlattenMatrixElements, FlattenNestedTupleMatrix, NestMatrixElements, NestTupleMatrix, + }; + pub use crate::{ + FlattenNestedTuple, IntoNestedTupleOption, NestTuple, NestTupleMut, NestTupleRef, + NestedTuple, NestedTupleChain, NestedTupleFlattenOption, NestedTupleFrom, NestedTupleIndex, + NestedTupleIndexMut, NestedTupleInsert, NestedTupleInto, NestedTupleMut, NestedTupleOption, + NestedTupleOptionFrom, NestedTupleOptionInto, NestedTupleOptionTryFrom, NestedTupleOptionTryInto, NestedTupleOptionWith, NestedTuplePopBack, NestedTuplePopFront, - NestedTuplePushBack, NestedTuplePushFront, NestedTupleRef, NestedTupleReplicate, - NestedTupleRow, NestedTupleRowMut, NestedTupleStartsWith, NestedTupleTryFrom, - NestedTupleTryInto, + NestedTuplePushBack, NestedTuplePushFront, NestedTupleRef, NestedTupleRemove, + NestedTupleReplicate, NestedTupleReverse, NestedTupleRow, NestedTupleRowMut, + NestedTupleSplit, NestedTupleStartsWith, NestedTupleTryFrom, NestedTupleTryInto, neple, + neplety, }; - #[cfg(feature = "from")] - pub use tuplities_from::{TupleFrom, TupleInto}; - #[cfg(feature = "hash")] - pub use tuplities_hash::TupleHash; - #[cfg(feature = "index")] - pub use tuplities_index::{FirstTupleIndex, LastTupleIndex, TupleIndex, TupleIndexMut}; - #[cfg(feature = "insert")] - pub use tuplities_insert::TupleInsert; - #[cfg(feature = "len")] - pub use tuplities_len::{PairTuple, SingletonTuple, TupleLen, UnitTuple}; - #[cfg(feature = "mut")] - pub use tuplities_mut::TupleMut; - #[cfg(feature = "mut")] - pub use tuplities_mut::TupleMutMap; - #[cfg(feature = "option")] - pub use tuplities_option::{IntoTupleOption, TupleOption}; - #[cfg(feature = "ord")] - pub use tuplities_ord::TupleOrd; - #[cfg(feature = "partial-eq")] - pub use tuplities_partial_eq::TuplePartialEq; - #[cfg(feature = "partial-ord")] - pub use tuplities_partial_ord::TuplePartialOrd; - #[cfg(feature = "pop-back")] - pub use tuplities_pop_back::{TupleMutBack, TuplePopBack, TupleRefBack}; - #[cfg(feature = "pop-front")] - pub use tuplities_pop_front::{TupleMutFront, TuplePopFront, TupleRefFront}; - #[cfg(feature = "push-back")] - pub use tuplities_push_back::TuplePushBack; - #[cfg(feature = "push-front")] - pub use tuplities_push_front::TuplePushFront; - #[cfg(feature = "ref")] - pub use tuplities_ref::TupleRef; - #[cfg(feature = "ref")] - pub use tuplities_ref::TupleRefMap; - #[cfg(feature = "remove")] - pub use tuplities_remove::TupleRemove; - #[cfg(feature = "replicate")] - pub use tuplities_replicate::TupleReplicate; - #[cfg(feature = "reverse")] - pub use tuplities_reverse::TupleReverse; - #[cfg(feature = "row")] - pub use tuplities_row::{FirstTupleRow, LastTupleRow, TupleRow, TupleRowMut}; - #[cfg(feature = "split")] - pub use tuplities_split::TupleSplit; - #[cfg(feature = "try-from")] - pub use tuplities_try_from::{TupleTryFrom, TupleTryInto}; } diff --git a/tuplities-flatten-nest/src/matrix.rs b/tuplities/src/matrix.rs similarity index 88% rename from tuplities-flatten-nest/src/matrix.rs rename to tuplities/src/matrix.rs index 1115c06..1faa802 100644 --- a/tuplities-flatten-nest/src/matrix.rs +++ b/tuplities/src/matrix.rs @@ -8,22 +8,22 @@ use crate::{flatten_nested::FlattenNestedTuple, nest::NestTuple}; /// and converts it into a nested tuple where each element is also nested /// (like `((A, (B,)), ((C, (D,)),))`). Both the outer and inner tuples are nested. /// -/// # Examples -/// -/// ``` -/// use tuplities_flatten_nest::NestTupleMatrix; -/// -/// let flat_matrix = ((1, 2), (3, 4)); -/// let nested_matrix = flat_matrix.nest_matrix(); -/// assert_eq!(nested_matrix, ((1, (2,)), ((3, (4,)),))); -/// ``` -/// /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait NestTupleMatrix { /// The nested matrix type. type NestedMatrix; /// Converts a flat tuple of flat tuples into a nested tuple of nested tuples. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestTupleMatrix; + /// + /// let flat_matrix = ((1, 2), (3, 4)); + /// let nested_matrix = flat_matrix.nest_matrix(); + /// assert_eq!(nested_matrix, ((1, (2,)), ((3, (4,)),))); + /// ``` fn nest_matrix(self) -> Self::NestedMatrix; } @@ -33,22 +33,22 @@ pub trait NestTupleMatrix { /// (like `((A, (B,)), ((C, (D,)),))`) and converts it into a flat tuple of flat tuples /// (like `((A, B), (C, D))`). Both the outer and inner tuples are flattened. /// -/// # Examples -/// -/// ``` -/// use tuplities_flatten_nest::FlattenNestedTupleMatrix; -/// -/// let nested_matrix = ((1, (2,)), ((3, (4,)),)); -/// let flat_matrix = nested_matrix.flatten_matrix(); -/// assert_eq!(flat_matrix, ((1, 2), (3, 4))); -/// ``` -/// /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait FlattenNestedTupleMatrix { /// The flattened matrix type. type FlattenedMatrix; /// Converts a nested tuple of nested tuples into a flat tuple of flat tuples. + /// + /// # Examples + /// + /// ``` + /// use tuplities::FlattenNestedTupleMatrix; + /// + /// let nested_matrix = ((1, (2,)), ((3, (4,)),)); + /// let flat_matrix = nested_matrix.flatten_matrix(); + /// assert_eq!(flat_matrix, ((1, 2), (3, 4))); + /// ``` fn flatten_matrix(self) -> Self::FlattenedMatrix; } @@ -62,6 +62,14 @@ pub trait NestMatrixElements { type Output: FlattenMatrixElements; /// Nest all elements in the structure + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestMatrixElements; + /// + /// assert_eq!(((1, 2),).nest_elements(), ((1, (2,)),)); + /// ``` fn nest_elements(self) -> Self::Output; } @@ -122,6 +130,14 @@ pub trait FlattenMatrixElements { type Output; /// Flatten all elements in the structure + /// + /// # Examples + /// + /// ``` + /// use tuplities::FlattenMatrixElements; + /// + /// assert_eq!(((1, (2,)),).flatten_elements(), ((1, 2),)); + /// ``` fn flatten_elements(self) -> Self::Output; } @@ -180,18 +196,6 @@ where mod tests { use super::*; - #[test] - fn test_nest_matrix_empty() { - let flat_matrix = (); - let _nested_matrix: () = flat_matrix.nest_matrix(); - } - - #[test] - fn test_flatten_matrix_empty() { - let nested_matrix = (); - let _flat_matrix: () = nested_matrix.flatten_matrix(); - } - #[test] fn test_nest_matrix_single() { let flat_matrix = ((1, 2, 3),); @@ -281,7 +285,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested matrix shape." + )] fn test_nested_matrix_structure() { let flat_matrix = ((1, 2), (3, 4)); let nested_matrix: ((i32, (i32,)), ((i32, (i32,)),)) = flat_matrix.nest_matrix(); diff --git a/tuplities-flatten-nest/src/nest.rs b/tuplities/src/nest.rs similarity index 60% rename from tuplities-flatten-nest/src/nest.rs rename to tuplities/src/nest.rs index 5a8c8d0..f190756 100644 --- a/tuplities-flatten-nest/src/nest.rs +++ b/tuplities/src/nest.rs @@ -1,8 +1,7 @@ //! Module providing the `NestTuple` trait for nesting flat tuples. -use crate::FlattenNestedTuple; +use crate::flatten_nested::FlattenNestedTuple; -#[tuplities_derive::impl_nest_tuple] /// A trait for nesting flat tuples into nested tuples. /// /// This trait takes a flat tuple like `(A, B, C)` and converts it @@ -14,9 +13,81 @@ pub trait NestTuple { type Nested: FlattenNestedTuple; /// Nests the flat tuple into a nested tuple. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestTuple; + /// + /// assert_eq!((7_u8,).nest(), (7_u8,)); + /// + /// # #[cfg(feature = "flatten-nest")] { + /// let flat = (1, 2, 3); + /// assert_eq!(flat.nest(), (1, (2, (3,)))); + /// # } + /// ``` fn nest(self) -> Self::Nested; } +/// A trait for nesting flat tuples into nested tuples of references. +/// +/// This trait takes a flat tuple like `(A, B, C)` and converts it +/// to a nested tuple of references like `(&A, (&B, (&C,)))`. +pub trait NestTupleRef { + /// The nested tuple type containing references. + type NestedRef<'a>: FlattenNestedTuple + where + Self: 'a; + + /// Nests the flat tuple into a nested tuple of references. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestTupleRef; + /// + /// let flat = (7,); + /// assert_eq!(flat.nest_ref(), (&7,)); + /// + /// # #[cfg(feature = "flatten-nest")] { + /// let flat = (1, 2, 3); + /// assert_eq!(flat.nest_ref(), (&1, (&2, (&3,)))); + /// # } + /// ``` + fn nest_ref(&self) -> Self::NestedRef<'_>; +} + +/// A trait for nesting flat tuples into nested tuples of mutable references. +/// +/// This trait takes a flat tuple like `(A, B, C)` and converts it +/// to a nested tuple of mutable references like `(&mut A, (&mut B, (&mut C,)))`. +pub trait NestTupleMut { + /// The nested tuple type containing mutable references. + type NestedMut<'a>: FlattenNestedTuple + where + Self: 'a; + + /// Nests the flat tuple into a nested tuple of mutable references. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestTupleMut; + /// + /// let mut single = (7,); + /// *single.nest_mut().0 = 9; + /// assert_eq!(single, (9,)); + /// + /// # #[cfg(feature = "flatten-nest")] { + /// let mut flat = (1, 2); + /// let nested = flat.nest_mut(); + /// *nested.1.0 = 20; + /// assert_eq!(flat, (1, 20)); + /// # } + /// ``` + fn nest_mut(&mut self) -> Self::NestedMut<'_>; +} + impl<'a, T> NestTuple for &'a T where T: NestTupleRef, @@ -41,35 +112,67 @@ where } } -/// A trait for nesting flat tuples into nested tuples of references. -/// -/// This trait takes a flat tuple like `(A, B, C)` and converts it -/// to a nested tuple of references like `(&A, (&B, (&C,)))`. -pub trait NestTupleRef { - /// The nested tuple type containing references. - type NestedRef<'a>: FlattenNestedTuple +impl NestTuple for () { + type Nested = (); + + #[inline] + fn nest(self) -> Self::Nested {} +} + +impl NestTupleRef for () { + type NestedRef<'a> + = () where Self: 'a; - /// Nests the flat tuple into a nested tuple of references. - fn nest_ref(&self) -> Self::NestedRef<'_>; + #[inline] + fn nest_ref(&self) -> Self::NestedRef<'_> {} } -/// A trait for nesting flat tuples into nested tuples of mutable references. -/// -/// This trait takes a flat tuple like `(A, B, C)` and converts it -/// to a nested tuple of mutable references like `(&mut A, (&mut B, (&mut C,)))`. -pub trait NestTupleMut { - /// The nested tuple type containing mutable references. - type NestedMut<'a>: FlattenNestedTuple +impl NestTupleMut for () { + type NestedMut<'a> + = () where Self: 'a; - /// Nests the flat tuple into a nested tuple of mutable references. - fn nest_mut(&mut self) -> Self::NestedMut<'_>; + #[inline] + fn nest_mut(&mut self) -> Self::NestedMut<'_> {} } -#[cfg(test)] +impl NestTuple for (N1,) { + type Nested = (N1,); + + #[inline] + fn nest(self) -> Self::Nested { + self + } +} + +impl NestTupleRef for (N1,) { + type NestedRef<'a> + = (&'a N1,) + where + Self: 'a; + + #[inline] + fn nest_ref(&self) -> Self::NestedRef<'_> { + (&self.0,) + } +} + +impl NestTupleMut for (N1,) { + type NestedMut<'a> + = (&'a mut N1,) + where + Self: 'a; + + #[inline] + fn nest_mut(&mut self) -> Self::NestedMut<'_> { + (&mut self.0,) + } +} + +#[cfg(all(test, feature = "flatten-nest"))] mod tests { use super::*; @@ -105,12 +208,6 @@ mod tests { assert_eq!(nested, (99,)); } - #[test] - fn test_nest_empty() { - let flat: () = (); - let _nested: () = flat.nest(); - } - #[test] fn test_nest_ref() { let flat = (1, 2, 3); diff --git a/tuplities-flatten-nest/src/nested_chain.rs b/tuplities/src/nested_chain.rs similarity index 81% rename from tuplities-flatten-nest/src/nested_chain.rs rename to tuplities/src/nested_chain.rs index 5b636f7..b5d4166 100644 --- a/tuplities-flatten-nest/src/nested_chain.rs +++ b/tuplities/src/nested_chain.rs @@ -4,29 +4,29 @@ /// /// This trait allows joining two nested tuples into a single nested tuple. /// It effectively appends the `other` nested tuple to `self`. -/// -/// # Examples -/// -/// ```rust -/// use tuplities_flatten_nest::NestedTupleChain; -/// -/// // Chaining (1, (2,)) and (3, (4,)) results in (1, (2, (3, (4,)))) -/// let t1 = (1, (2,)); -/// let t2 = (3, (4,)); -/// let chained = t1.chain(t2); -/// assert_eq!(chained, (1, (2, (3, (4,))))); -/// -/// // Chaining (1, (2, ())) and (3, (4, ())) results in (1, (2, (3, (4, ())))) -/// let t3 = (1, (2, ())); -/// let t4 = (3, (4, ())); -/// let chained2 = t3.chain(t4); -/// assert_eq!(chained2, (1, (2, (3, (4, ()))))); -/// ``` pub trait NestedTupleChain { /// The resulting chained nested tuple type. type Chained; /// Chains `other` to the end of `self`. + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleChain; + /// + /// // Chaining (1, (2,)) and (3, (4,)) results in (1, (2, (3, (4,)))) + /// let t1 = (1, (2,)); + /// let t2 = (3, (4,)); + /// let chained = t1.chain(t2); + /// assert_eq!(chained, (1, (2, (3, (4,))))); + /// + /// // Chaining (1, (2, ())) and (3, (4, ())) results in (1, (2, (3, (4, ())))) + /// let t3 = (1, (2, ())); + /// let t4 = (3, (4, ())); + /// let chained2 = t3.chain(t4); + /// assert_eq!(chained2, (1, (2, (3, (4, ()))))); + /// ``` fn chain(self, other: Other) -> Self::Chained; } diff --git a/tuplities-flatten-nest/src/nested_index.rs b/tuplities/src/nested_index.rs similarity index 62% rename from tuplities-flatten-nest/src/nested_index.rs rename to tuplities/src/nested_index.rs index 914315c..ff92b59 100644 --- a/tuplities-flatten-nest/src/nested_index.rs +++ b/tuplities/src/nested_index.rs @@ -1,54 +1,71 @@ //! Module providing indexing traits for nested tuples. -use crate::flatten_nested::FlattenNestedTuple; +use crate::NestedTuple; /// A trait for indexing into nested tuples at compile-time known positions. /// -/// This trait allows accessing elements at specific flat indices `Idx` -/// from nested tuples, where `Idx` is a compile-time constant from `typenum`. -/// The index corresponds to the position in the flattened tuple. -/// -/// # Examples -/// -/// ``` -/// use tuplities_flatten_nest::NestedTupleIndex; -/// use typenum::U1; -/// -/// let nested = (1, (2, (3,))); -/// let element = NestedTupleIndex::::nested_index(&nested); -/// assert_eq!(*element, 2); -/// ``` +/// The `typenum` index counts heads from zero. A tuple-valued head occupies one position. /// /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. -pub trait NestedTupleIndex: FlattenNestedTuple { - /// The type of the element at flat index `Idx`. +pub trait NestedTupleIndex: NestedTuple { + /// The element type selected by `Idx`. type Element; /// Returns a reference to the element at flat index `Idx`. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTupleIndex; + /// use typenum::U1; + /// + /// let nested = (1, (2, (3,))); + /// let element = NestedTupleIndex::::nested_index(&nested); + /// assert_eq!(*element, 2); + /// ``` + /// + /// Indices outside the list have no implementation. + /// + /// ```compile_fail + /// use tuplities::NestedTupleIndex; + /// use typenum::U2; + /// + /// let values = (1_u8, (2_u16, ())); + /// let _ = NestedTupleIndex::::nested_index(&values); + /// ``` fn nested_index(&self) -> &Self::Element; } /// A trait for mutable indexing into nested tuples at compile-time known positions. /// -/// This trait allows mutable access to elements at specific flat indices `Idx` -/// from nested tuples, where `Idx` is a compile-time constant from `typenum`. -/// The index corresponds to the position in the flattened tuple. -/// -/// # Examples -/// -/// ``` -/// use tuplities_flatten_nest::NestedTupleIndexMut; -/// use typenum::U1; -/// -/// let mut nested = (1, (2, (3,))); -/// let element = NestedTupleIndexMut::::nested_index_mut(&mut nested); -/// *element = 20; -/// assert_eq!(nested, (1, (20, (3,)))); -/// ``` +/// The `typenum` index counts heads from zero. A tuple-valued head occupies one position. /// /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait NestedTupleIndexMut: NestedTupleIndex { /// Returns a mutable reference to the element at flat index `Idx`. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTupleIndexMut; + /// use typenum::U1; + /// + /// let mut nested = (1, (2, (3,))); + /// let element = NestedTupleIndexMut::::nested_index_mut(&mut nested); + /// *element = 20; + /// assert_eq!(nested, (1, (20, (3,)))); + /// ``` + /// + /// Shared tails cannot provide mutable access. + /// + /// ```compile_fail + /// use tuplities::NestedTupleIndexMut; + /// use typenum::U1; + /// + /// let tail = (1_u8,); + /// let mut values = (0_u8, &tail); + /// let _ = NestedTupleIndexMut::::nested_index_mut(&mut values); + /// ``` fn nested_index_mut(&mut self) -> &mut Self::Element; } @@ -62,7 +79,7 @@ impl NestedTupleIndex for (Head,) { impl NestedTupleIndex for (Head, Tail) where - (Head, Tail): FlattenNestedTuple, + Tail: NestedTuple, { type Element = Head; @@ -73,7 +90,6 @@ where impl NestedTupleIndex> for (Head, Tail) where - (Head, Tail): FlattenNestedTuple, typenum::UInt: core::ops::Sub, Tail: NestedTupleIndex>>, { @@ -92,7 +108,7 @@ impl NestedTupleIndexMut for (Head,) { impl NestedTupleIndexMut for (Head, Tail) where - (Head, Tail): FlattenNestedTuple, + Tail: NestedTuple, { fn nested_index_mut(&mut self) -> &mut Self::Element { &mut self.0 @@ -101,7 +117,6 @@ where impl NestedTupleIndexMut> for (Head, Tail) where - (Head, Tail): FlattenNestedTuple, typenum::UInt: core::ops::Sub, Tail: NestedTupleIndexMut>>, { @@ -110,6 +125,28 @@ where } } +impl + ?Sized> NestedTupleIndex for &T { + type Element = T::Element; + + fn nested_index(&self) -> &Self::Element { + T::nested_index(*self) + } +} + +impl + ?Sized> NestedTupleIndex for &mut T { + type Element = T::Element; + + fn nested_index(&self) -> &Self::Element { + T::nested_index(*self) + } +} + +impl + ?Sized> NestedTupleIndexMut for &mut T { + fn nested_index_mut(&mut self) -> &mut Self::Element { + T::nested_index_mut(*self) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/tuplities-flatten-nest/src/nested_into_vec.rs b/tuplities/src/nested_into_vec.rs similarity index 85% rename from tuplities-flatten-nest/src/nested_into_vec.rs rename to tuplities/src/nested_into_vec.rs index 96030a8..8124715 100644 --- a/tuplities-flatten-nest/src/nested_into_vec.rs +++ b/tuplities/src/nested_into_vec.rs @@ -15,17 +15,31 @@ pub trait NestedTupleIntoVec { /// # Examples /// /// ```rust - /// use tuplities_flatten_nest::NestedTupleIntoVec; + /// # if cfg!(feature = "alloc") { + /// use tuplities::NestedTupleIntoVec; /// /// let nested = (1, (2, (3,))); /// let vec: Vec = nested.into_vec(); /// assert_eq!(vec, vec![1, 2, 3]); + /// # } /// ``` fn into_vec(self) -> Vec; /// Appends the elements of the nested tuple to an existing vector. /// /// This is used internally by `into_vec` for efficiency but can also be used directly. + /// + /// # Examples + /// + /// ```rust + /// # if cfg!(feature = "alloc") { + /// use tuplities::NestedTupleIntoVec; + /// + /// let mut buf = vec![1, 2]; + /// (3, (4, (5,))).append_to_vec(&mut buf); + /// assert_eq!(buf, vec![1, 2, 3, 4, 5]); + /// # } + /// ``` fn append_to_vec(self, buf: &mut Vec); } diff --git a/tuplities-flatten-nest/src/nested_option.rs b/tuplities/src/nested_option.rs similarity index 78% rename from tuplities-flatten-nest/src/nested_option.rs rename to tuplities/src/nested_option.rs index 68943bf..b5ddc2d 100644 --- a/tuplities-flatten-nest/src/nested_option.rs +++ b/tuplities/src/nested_option.rs @@ -1,24 +1,6 @@ //! Module providing option transposition traits for nested tuples. -/// A trait for transposing nested tuples of options into options of nested tuples. -/// -/// This trait takes a nested tuple where each element is an `Option` and transposes it -/// into an `Option` of the nested tuple with the inner types. Returns `Some(nested_tuple)` -/// if all elements are `Some`, otherwise `None`. -/// -/// # Examples -/// -/// ``` -/// use tuplities_flatten_nest::NestedTupleOption; -/// -/// let nested_options = (Some(1), (Some(2), (Some(3),))); -/// let transposed: Option<(i32, (i32, (i32,)))> = nested_options.transpose(); -/// assert_eq!(transposed, Some((1, (2, (3,))))); -/// -/// let nested_options = (Some(1), (None, (Some(3),))); -/// let transposed: Option<(i32, (i32, (i32,)))> = nested_options.transpose(); -/// assert_eq!(transposed, None); -/// ``` +/// Transposes nested tuples of options into options of nested tuples. /// /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait NestedTupleOption { @@ -28,6 +10,20 @@ pub trait NestedTupleOption { /// Transposes the nested tuple of options into an option of the nested tuple. /// /// Returns `Some(nested_tuple)` if all elements are `Some`, otherwise `None`. + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleOption; + /// + /// let nested_options = (Some(1), (Some(2), (Some(3),))); + /// let transposed: Option<(i32, (i32, (i32,)))> = nested_options.transpose(); + /// assert_eq!(transposed, Some((1, (2, (3,))))); + /// + /// let nested_options = (Some(1), (None, (Some(3),))); + /// let transposed: Option<(i32, (i32, (i32,)))> = nested_options.transpose(); + /// assert_eq!(transposed, None); + /// ``` fn transpose(self) -> Option; } @@ -38,9 +34,37 @@ pub trait NestedTupleOptionWith: NestedTupleOption { type SameDepth; /// Returns the first `H` corresponding to the first `None` in `self`. + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleOptionWith; + /// + /// let options = (Some(1), (None::, (Some(3),))); + /// let hom = (10, (20, (30,))); + /// assert_eq!(options.first_none_with(hom), Some(20)); + /// + /// let options = (Some(1), (Some(2), (Some(3),))); + /// let hom = (10, (20, (30,))); + /// assert_eq!(options.first_none_with(hom), None); + /// ``` fn first_none_with(self, xs: Self::SameDepth) -> Option; /// Returns the first `H` corresponding to the first `Some` in `self`. + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleOptionWith; + /// + /// let options = (None::, (Some(2), (Some(3),))); + /// let hom = (10, (20, (30,))); + /// assert_eq!(options.first_some_with(hom), Some(20)); + /// + /// let options = (None::, (None::, (None::,))); + /// let hom = (10, (20, (30,))); + /// assert_eq!(options.first_some_with(hom), None); + /// ``` fn first_some_with(self, xs: Self::SameDepth) -> Option; /// Like `transpose`, but returns a `Result` with `Ok(Transposed)` when all elements @@ -52,6 +76,20 @@ pub trait NestedTupleOptionWith: NestedTupleOption { /// corresponding element of the parallel homogeneous nested tuple `xs` for the first /// `None` encountered (traversed left-to-right, depth-first in the nested pair /// representation). + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleOptionWith; + /// + /// let options = (Some(1), (Some(2), (Some(3),))); + /// let hom = (10, (20, (30,))); + /// assert_eq!(options.transpose_or(hom), Ok((1, (2, (3,))))); + /// + /// let options = (Some(1), (None::, (Some(3),))); + /// let hom = (10, (20, (30,))); + /// assert_eq!(options.transpose_or(hom), Err(20)); + /// ``` fn transpose_or(self, xs: Self::SameDepth) -> Result; } @@ -68,7 +106,7 @@ pub trait IntoNestedTupleOption { /// # Examples /// /// ```rust - /// use tuplities_flatten_nest::IntoNestedTupleOption; + /// use tuplities::IntoNestedTupleOption; /// /// let nested = (1, (2, (3,))); /// let into_options: (Option, (Option, (Option,))) = nested.into_options(); @@ -238,20 +276,7 @@ where } } -/// A trait for flattening nested tuples of double options into nested tuples of options. -/// -/// This trait takes a nested tuple where each element is an `Option>` and flattens it -/// into a nested tuple where each element is an `Option`. -/// -/// # Examples -/// -/// ``` -/// use tuplities_flatten_nest::NestedTupleFlattenOption; -/// -/// let nested_double_options = (Some(Some(1)), (Some(None), (None,))); -/// let flattened: (Option, (Option, (Option,))) = nested_double_options.flatten_options(); -/// assert_eq!(flattened, (Some(1), (None, (None,)))); -/// ``` +/// Flattens nested tuples of double options into nested tuples of options. /// /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait NestedTupleFlattenOption { @@ -259,6 +284,17 @@ pub trait NestedTupleFlattenOption { type FlattenedOptions: IntoNestedTupleOption; /// Flattens the nested tuple of double options into a nested tuple of options. + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleFlattenOption; + /// + /// let nested_double_options = (Some(Some(1)), (Some(None), (None,))); + /// let flattened: (Option, (Option, (Option,))) = + /// nested_double_options.flatten_options(); + /// assert_eq!(flattened, (Some(1), (None, (None,)))); + /// ``` fn flatten_options(self) -> Self::FlattenedOptions; } @@ -347,7 +383,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested three-level `Option` shape." + )] fn test_into_options_three() { let nested = (1, (2, (3,))); let into_options: (Option, (Option, (Option,))) = nested.into_options(); @@ -445,10 +484,33 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested three-level `Option` shape." + )] fn test_flatten_options() { let nested = (Some(Some(1)), (Some(None), (None,))); let flattened: (Option, (Option, (Option,))) = nested.flatten_options(); assert_eq!(flattened, (Some(1), (None, (None,)))); } + + #[test] + fn test_first_none_with_selects_singleton_terminal_label() { + let options = (Some(1), (None::,)); + let labels = (NoCopy(31), (NoCopy(47),)); + assert_eq!(options.first_none_with(labels), Some(NoCopy(47))); + } + + #[test] + fn test_first_some_with_selects_singleton_terminal_label() { + let options = (None::, (Some(1),)); + let labels = (NoCopy(31), (NoCopy(47),)); + assert_eq!(options.first_some_with(labels), Some(NoCopy(47))); + } + + #[test] + fn test_flatten_options_preserves_singleton_terminal_payload() { + let options = (None::>, (Some(Some(NoCopy(47))),)); + assert_eq!(options.flatten_options(), (None, (Some(NoCopy(47)),))); + } } diff --git a/tuplities-flatten-nest/src/nested_option_try_from.rs b/tuplities/src/nested_option_try_from.rs similarity index 78% rename from tuplities-flatten-nest/src/nested_option_try_from.rs rename to tuplities/src/nested_option_try_from.rs index 322e239..60121e0 100644 --- a/tuplities-flatten-nest/src/nested_option_try_from.rs +++ b/tuplities/src/nested_option_try_from.rs @@ -15,6 +15,23 @@ pub trait NestedTupleOptionTryFrom: Sized { /// # Errors /// /// Returns an error of type `E` if any element conversion fails. + /// + /// # Examples + /// + /// ```rust + /// use core::num::TryFromIntError; + /// use tuplities::NestedTupleOptionTryFrom; + /// + /// let source = (Some(1_u16), (None::, (Some(3_u16),))); + /// let result: Result<(Option, (Option, (Option,))), TryFromIntError> = + /// <(Option, (Option, (Option,)))>::nested_tuple_option_try_from(source); + /// assert_eq!(result, Ok((Some(1_u8), (None, (Some(3),))))); + /// + /// let source = (Some(1_u16), (Some(300_u16), (Some(3_u16),))); + /// let result: Result<(Option, (Option, (Option,))), TryFromIntError> = + /// <(Option, (Option, (Option,)))>::nested_tuple_option_try_from(source); + /// assert!(matches!(result, Err(_))); + /// ``` fn nested_tuple_option_try_from(other: T) -> Result; } @@ -29,6 +46,23 @@ pub trait NestedTupleOptionTryInto: Sized { /// # Errors /// /// Returns an error of type `E` if any element conversion fails. + /// + /// # Examples + /// + /// ```rust + /// use core::num::TryFromIntError; + /// use tuplities::NestedTupleOptionTryInto; + /// + /// let source = (Some(1_u16), (None::, (Some(3_u16),))); + /// let result: Result<(Option, (Option, (Option,))), TryFromIntError> = + /// source.nested_tuple_option_try_into(); + /// assert_eq!(result, Ok((Some(1_u8), (None, (Some(3),))))); + /// + /// let source = (Some(1_u16), (Some(300_u16), (Some(3_u16),))); + /// let result: Result<(Option, (Option, (Option,))), TryFromIntError> = + /// source.nested_tuple_option_try_into(); + /// assert!(matches!(result, Err(_))); + /// ``` fn nested_tuple_option_try_into(self) -> Result; } @@ -83,6 +117,17 @@ where /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait NestedTupleOptionFrom: Sized { /// Converts `other` into `Self` by applying `From` element-wise. + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleOptionFrom; + /// + /// let source = (Some(1_u8), (None::, (Some(3_u8),))); + /// let result: (Option, (Option, (Option,))) = + /// <(Option, (Option, (Option,)))>::nested_tuple_option_from(source); + /// assert_eq!(result, (Some(1_u16), (None, (Some(3),)))); + /// ``` fn nested_tuple_option_from(other: T) -> Self; } @@ -93,12 +138,23 @@ pub trait NestedTupleOptionFrom: Sized { /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait NestedTupleOptionInto: Sized { /// Converts `self` into `T` by applying `Into` element-wise. + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleOptionInto; + /// + /// let source = (Some(1_u8), (None::, (Some(3_u8),))); + /// let result: (Option, (Option, (Option,))) = + /// source.nested_tuple_option_into(); + /// assert_eq!(result, (Some(1_u16), (None, (Some(3),)))); + /// ``` fn nested_tuple_option_into(self) -> T; } impl NestedTupleOptionInto<()> for () { #[inline] - fn nested_tuple_option_into(self) -> () {} + fn nested_tuple_option_into(self) {} } impl NestedTupleOptionInto<(Option,)> for (Option,) @@ -222,7 +278,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested three-level `Option` shape." + )] fn test_nested_tuple_option_try_from_nested() { let source = (Some(1u8), (Some(2u8), (Some(3u8),))); let target: Result<(Option, (Option, (Option,))), MyError> = @@ -246,7 +305,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested three-level `Option` shape." + )] fn test_nested_tuple_option_from_nested() { let source = (Some(1u8), (Some(2u8), (Some(3u8),))); let target: (Option, (Option, (Option,))) = @@ -312,7 +374,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested three-level `Option` shape." + )] fn test_nested_tuple_option_try_from_custom_wrapper_error_nested() { let source = (Some(WrapI16(256)), (Some(WrapI16(1)), (Some(WrapI16(2)),))); let target: Result<(Option, (Option, (Option,))), WrapErr> = @@ -321,7 +386,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested three-level `Option` shape." + )] fn test_nested_tuple_option_from_with_none() { let source = (None::, (Some(2u8), (None::,))); let target: (Option, (Option, (Option,))) = @@ -330,7 +398,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested three-level `Option` shape." + )] fn test_nested_tuple_option_try_from_with_none() { let source = (None::, (Some(2u8), (None::,))); let target: Result<(Option, (Option, (Option,))), MyError> = diff --git a/tuplities-flatten-nest/src/nested_push_pop.rs b/tuplities/src/nested_push_pop.rs similarity index 71% rename from tuplities-flatten-nest/src/nested_push_pop.rs rename to tuplities/src/nested_push_pop.rs index cb6db29..5cb1344 100644 --- a/tuplities-flatten-nest/src/nested_push_pop.rs +++ b/tuplities/src/nested_push_pop.rs @@ -1,5 +1,7 @@ //! Module providing pop/push traits for nested tuples. +use crate::NestedTuple; + /// A trait for popping the front element of a nested tuple. pub trait NestedTuplePopFront { /// The type of the first element. @@ -9,6 +11,22 @@ pub trait NestedTuplePopFront { type Tail; /// Consume the nested tuple and return the first element and the remaining nested tuple. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTuplePopFront; + /// + /// let (front, tail) = (1, (2, (3,))).nested_pop_front(); + /// assert_eq!(front, 1); + /// assert_eq!(tail, (2, (3,))); + /// + /// let shared = (1, (2, (3,))); + /// let (shared_front, shared_tail) = (&shared).nested_pop_front(); + /// assert_eq!(*shared_front, 1); + /// assert_eq!(*shared_tail, (2, (3,))); + /// assert_eq!(shared, (1, (2, (3,)))); + /// ``` fn nested_pop_front(self) -> (Self::Front, Self::Tail); } @@ -21,6 +39,16 @@ pub trait NestedTuplePopBack { type Back; /// Consume the nested tuple and return the remaining nested tuple and the last element. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTuplePopBack; + /// + /// let (init, back) = (1, (2, (3,))).nested_pop_back(); + /// assert_eq!(init, (1, (2,))); + /// assert_eq!(back, 3); + /// ``` fn nested_pop_back(self) -> (Self::Init, Self::Back); } @@ -30,6 +58,18 @@ pub trait NestedTuplePushFront { type Output; /// Push an element to the front. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTuplePushFront; + /// + /// let pushed = ().nested_push_front(1); + /// assert_eq!(pushed, (1,)); + /// + /// let pushed = (2, (3,)).nested_push_front(1); + /// assert_eq!(pushed, (1, (2, (3,)))); + /// ``` fn nested_push_front(self, item: Item) -> Self::Output; } @@ -39,6 +79,18 @@ pub trait NestedTuplePushBack { type Output; /// Push an element to the back. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTuplePushBack; + /// + /// let pushed = ().nested_push_back(1); + /// assert_eq!(pushed, (1,)); + /// + /// let pushed = (1, (2, (3,))).nested_push_back(4); + /// assert_eq!(pushed, (1, (2, (3, (4,))))); + /// ``` fn nested_push_back(self, item: Item) -> Self::Output; } @@ -164,6 +216,60 @@ impl NestedTuplePushFront for (Head, Tail) { } } +impl NestedTuplePushFront for &() { + type Output = (Item,); + + #[inline] + fn nested_push_front(self, item: Item) -> Self::Output { + (item,) + } +} + +impl NestedTuplePushFront for &mut () { + type Output = (Item,); + + #[inline] + fn nested_push_front(self, item: Item) -> Self::Output { + (item,) + } +} + +impl<'a, Item, Head> NestedTuplePushFront for &'a (Head,) { + type Output = (Item, &'a (Head,)); + + #[inline] + fn nested_push_front(self, item: Item) -> Self::Output { + (item, self) + } +} + +impl<'a, Item, Head> NestedTuplePushFront for &'a mut (Head,) { + type Output = (Item, &'a mut (Head,)); + + #[inline] + fn nested_push_front(self, item: Item) -> Self::Output { + (item, self) + } +} + +impl<'a, Item, Head, Tail: NestedTuple> NestedTuplePushFront for &'a (Head, Tail) { + type Output = (Item, &'a (Head, Tail)); + + #[inline] + fn nested_push_front(self, item: Item) -> Self::Output { + (item, self) + } +} + +impl<'a, Item, Head, Tail: NestedTuple> NestedTuplePushFront for &'a mut (Head, Tail) { + type Output = (Item, &'a mut (Head, Tail)); + + #[inline] + fn nested_push_front(self, item: Item) -> Self::Output { + (item, self) + } +} + impl NestedTuplePushBack for () { type Output = (Item,); diff --git a/tuplities-flatten-nest/src/nested_ref.rs b/tuplities/src/nested_ref.rs similarity index 87% rename from tuplities-flatten-nest/src/nested_ref.rs rename to tuplities/src/nested_ref.rs index c47ff57..56c6ff0 100644 --- a/tuplities-flatten-nest/src/nested_ref.rs +++ b/tuplities/src/nested_ref.rs @@ -10,6 +10,15 @@ pub trait NestedTupleRef { Self: 'a; /// Returns a nested tuple containing references to each leaf value. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTupleRef; + /// + /// let nested = (1, (2, (3,))); + /// assert_eq!(nested.nested_tuple_ref(), (&1, (&2, (&3,)))); + /// ``` fn nested_tuple_ref(&self) -> Self::Ref<'_>; } @@ -21,6 +30,17 @@ pub trait NestedTupleMut { Self: 'a; /// Returns a nested tuple containing mutable references to each leaf value. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTupleMut; + /// + /// let mut nested = (1, (2,)); + /// let mut refs = nested.nested_tuple_mut(); + /// *refs.1.0 = 20; + /// assert_eq!(nested, (1, (20,))); + /// ``` fn nested_tuple_mut(&mut self) -> Self::Mut<'_>; } diff --git a/tuplities-flatten-nest/src/nested_replicate.rs b/tuplities/src/nested_replicate.rs similarity index 82% rename from tuplities-flatten-nest/src/nested_replicate.rs rename to tuplities/src/nested_replicate.rs index ce5c6d5..6cf7c84 100644 --- a/tuplities-flatten-nest/src/nested_replicate.rs +++ b/tuplities/src/nested_replicate.rs @@ -2,11 +2,8 @@ /// A trait for replicating a value into a nested tuple. /// -/// This trait allows creating a nested tuple where all elements are the same value. -/// The implementation follows the same optimization as `TupleReplicate`: for empty tuples -/// and single-element tuples, no `Clone` bound is required. For nested tuples with 2+ -/// elements, `Clone` is required but the original value is moved to the last -/// position to minimize clones. +/// The empty and single-element forms take no `Clone` bound; longer forms clone for all but the +/// last position and move the value there. /// /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait NestedTupleReplicate { @@ -15,7 +12,7 @@ pub trait NestedTupleReplicate { /// # Examples /// /// ```rust - /// use tuplities_flatten_nest::NestedTupleReplicate; + /// use tuplities::NestedTupleReplicate; /// /// let nested: (i32, (i32, (i32,))) = NestedTupleReplicate::nested_tuple_replicate(42); /// assert_eq!(nested, (42, (42, (42,)))); diff --git a/tuplities/src/nested_reverse.rs b/tuplities/src/nested_reverse.rs new file mode 100644 index 0000000..1e086b3 --- /dev/null +++ b/tuplities/src/nested_reverse.rs @@ -0,0 +1,134 @@ +//! Module providing the `NestedTupleReverse` trait for reversing nested tuples. + +use crate::NestedTuple; + +/// Consumes owned tuple nodes into singleton-terminated reverse order. +pub trait NestedTupleReverse: NestedTuple { + /// The reversed nested tuple type. + type Output: NestedTuple; + + /// Consumes the list and returns its elements in reverse order. + /// + /// # Examples + /// + /// ``` + /// use tuplities::{neple, neplety, NestedTupleReverse}; + /// + /// let () = ().nested_reverse(); + /// assert_eq!(neple!(7_u8).nested_reverse(), (7,)); + /// assert_eq!((7_u8, ()).nested_reverse(), (7,)); + /// + /// let original = ((1_u8, 2_u16), (3_u32, (false, ()))); + /// let reversed: neplety!(bool, u32, (u8, u16)) = original.nested_reverse(); + /// assert_eq!(reversed, neple!(false, 3, (1, 2))); + /// ``` + /// + /// ```compile_fail + /// use tuplities::{neple, NestedTupleReverse}; + /// + /// let tail = neple!(2_u16, 3_u32); + /// let _ = NestedTupleReverse::nested_reverse((1_u8, &tail)); + /// ``` + fn nested_reverse(self) -> Self::Output; +} + +/// Reverses an owned tail into a nonempty singleton-terminated accumulator. +#[doc(hidden)] +pub trait NestedReverseAccumulator: NestedTuple { + /// The fully reversed nested tuple type. + type Output: NestedTuple; + + /// Consumes the tail and returns its elements reversed ahead of `acc`. + fn nested_reverse_into(self, acc: Acc) -> Self::Output; +} + +impl NestedTupleReverse for () { + type Output = (); + + #[inline] + fn nested_reverse(self) -> Self::Output {} +} + +impl NestedTupleReverse for (Head,) { + type Output = (Head,); + + #[inline] + fn nested_reverse(self) -> Self::Output { + self + } +} + +impl NestedTupleReverse for (Head, Tail) +where + Tail: NestedReverseAccumulator<(Head,)>, +{ + type Output = >::Output; + + #[inline] + fn nested_reverse(self) -> Self::Output { + let (head, tail) = self; + tail.nested_reverse_into((head,)) + } +} + +impl NestedReverseAccumulator for () +where + Acc: NestedTuple, +{ + type Output = Acc; + + #[inline] + fn nested_reverse_into(self, acc: Acc) -> Self::Output { + acc + } +} + +impl NestedReverseAccumulator for (Head,) +where + Acc: NestedTuple, +{ + type Output = (Head, Acc); + + #[inline] + fn nested_reverse_into(self, acc: Acc) -> Self::Output { + (self.0, acc) + } +} + +impl NestedReverseAccumulator for (Head, Tail) +where + Acc: NestedTuple, + Tail: NestedReverseAccumulator<(Head, Acc)>, +{ + type Output = >::Output; + + #[inline] + fn nested_reverse_into(self, acc: Acc) -> Self::Output { + let (head, tail) = self; + tail.nested_reverse_into((head, acc)) + } +} + +impl NestedReverseAccumulator for &() +where + Acc: NestedTuple, +{ + type Output = Acc; + + #[inline] + fn nested_reverse_into(self, acc: Acc) -> Self::Output { + acc + } +} + +impl NestedReverseAccumulator for &mut () +where + Acc: NestedTuple, +{ + type Output = Acc; + + #[inline] + fn nested_reverse_into(self, acc: Acc) -> Self::Output { + acc + } +} diff --git a/tuplities-flatten-nest/src/nested_row.rs b/tuplities/src/nested_row.rs similarity index 69% rename from tuplities-flatten-nest/src/nested_row.rs rename to tuplities/src/nested_row.rs index 4872328..5ae229b 100644 --- a/tuplities-flatten-nest/src/nested_row.rs +++ b/tuplities/src/nested_row.rs @@ -1,13 +1,25 @@ //! Module providing nested row accessors for nested tuple-of-tuples. -use crate::{NestedTupleIndex, NestedTupleIndexMut, NestedTupleMut, NestedTupleRef}; +use crate::{NestedTuple, NestedTupleIndex, NestedTupleIndexMut, NestedTupleMut, NestedTupleRef}; -/// Skeleton trait for nested row retrieval. -pub trait NestedTupleRow { - /// The nested tuple type representing a row across the matrix. - type RowType: NestedTupleRef; +/// Retrieves one index across a recursive tuple of lists. +pub trait NestedTupleRow: NestedTuple { + /// The recursive tuple of selected element types. + type RowType: NestedTuple + NestedTupleRef; /// Return the nested row at the given index. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTupleRow; + /// use typenum::U0; + /// + /// let matrix = ((1, (2,)), ((3, (4,)),)); + /// let row = NestedTupleRow::::nested_tuple_row(&matrix); + /// assert_eq!(*row.0, 1); + /// assert_eq!(*row.1.0, 3); + /// ``` fn nested_tuple_row(&self) -> ::Ref<'_>; } @@ -46,9 +58,22 @@ where } } -/// Skeleton trait for mutable nested row retrieval. +/// Retrieves mutable references at one index across a recursive tuple of lists. pub trait NestedTupleRowMut: NestedTupleRow { /// Returns a nested tuple containing mutable references to the row elements. + /// + /// # Examples + /// + /// ``` + /// use tuplities::NestedTupleRowMut; + /// use typenum::U0; + /// + /// let mut matrix = ((1, (2,)), ((3, (4,)),)); + /// let row = NestedTupleRowMut::::nested_tuple_row_mut(&mut matrix); + /// *row.0 = 10; + /// *row.1.0 = 30; + /// assert_eq!(matrix, ((10, (2,)), ((30, (4,)),))); + /// ``` fn nested_tuple_row_mut(&mut self) -> ::Mut<'_>; } @@ -62,7 +87,7 @@ where impl NestedTupleRowMut for (T1,) where - T1: NestedTupleIndexMut + NestedTupleMut, + T1: NestedTupleIndexMut, { #[inline] fn nested_tuple_row_mut(&mut self) -> ::Mut<'_> { @@ -73,7 +98,7 @@ where impl NestedTupleRowMut for (Head, Tail) where - Head: NestedTupleIndexMut + NestedTupleMut, + Head: NestedTupleIndexMut, Tail: NestedTupleRowMut, Tail::RowType: NestedTupleMut, { @@ -84,17 +109,33 @@ where } } +impl + ?Sized> NestedTupleRow for &T { + type RowType = T::RowType; + + fn nested_tuple_row(&self) -> ::Ref<'_> { + T::nested_tuple_row(*self) + } +} + +impl + ?Sized> NestedTupleRow for &mut T { + type RowType = T::RowType; + + fn nested_tuple_row(&self) -> ::Ref<'_> { + T::nested_tuple_row(*self) + } +} + +impl + ?Sized> NestedTupleRowMut for &mut T { + fn nested_tuple_row_mut(&mut self) -> ::Mut<'_> { + T::nested_tuple_row_mut(*self) + } +} + #[cfg(test)] mod tests { use super::*; use typenum::{U0, U1, U2}; - #[test] - fn test_nested_row_empty() { - let matrix: () = (); - let _row: () = NestedTupleRow::::nested_tuple_row(&matrix); - } - #[test] fn test_nested_row_single_element() { let matrix: ((i32, (i32, (i32,))),) = ((10, (20, (30,))),); @@ -107,7 +148,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested matrix shape." + )] fn test_nested_row_two_outer_elements() { // outer nested pair with two elements: (Head, (Second,)) let matrix: ((i32, (i32,)), ((i32, (i32,)),)) = ((1, (2,)), ((3, (4,)),)); @@ -120,7 +164,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested matrix shape." + )] fn test_nested_row_three_outer_elements() { // outer nested pair with three elements: (A, (B, (C,))) let matrix: ( @@ -145,7 +192,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested matrix shape." + )] fn test_nested_row_mut_modify_two_elements() { let mut matrix: ((i32, (i32,)), ((i32, (i32,)),)) = ((1, (2,)), ((3, (4,)),)); let row0 = NestedTupleRowMut::::nested_tuple_row_mut(&mut matrix); @@ -160,7 +210,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested matrix shape." + )] fn test_nested_row_mut_modify_three_elements() { let mut matrix: ( (i32, (i32, (i32,))), @@ -178,7 +231,10 @@ mod tests { } #[test] - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "The fixture specifies the nested matrix shape." + )] fn test_nested_row_mixed_types_ref() { let matrix: ((i32, (&str,)), ((i32, (&str,)),)) = ((1, ("a",)), ((2, ("b",)),)); let row0 = NestedTupleRow::::nested_tuple_row(&matrix); diff --git a/tuplities-flatten-nest/src/nested_starts_with.rs b/tuplities/src/nested_starts_with.rs similarity index 100% rename from tuplities-flatten-nest/src/nested_starts_with.rs rename to tuplities/src/nested_starts_with.rs diff --git a/tuplities/src/nested_structure.rs b/tuplities/src/nested_structure.rs new file mode 100644 index 0000000..5334955 --- /dev/null +++ b/tuplities/src/nested_structure.rs @@ -0,0 +1,247 @@ +//! Module providing the consuming structural split, insert, and remove traits. + +use crate::{NestedTuple, NestedTuplePushFront}; + +/// Splits at `typenum` indices through `NestedTuple::LEN`, singleton-terminating the prefix and preserving the suffix. +pub trait NestedTupleSplit: NestedTuple { + /// The rebuilt prefix holding the first `Idx` heads, singleton-terminated. + type Prefix: NestedTuple; + + /// The untouched suffix starting at `Idx`, preserving the input representation. + type Suffix: NestedTuple; + + /// Consumes the list, returning the prefix and the suffix. + /// + /// # Examples + /// + /// ``` + /// use tuplities::{neple, NestedTupleSplit}; + /// use typenum::{U0, U2}; + /// + /// assert_eq!(NestedTupleSplit::::nested_split(()), ((), ())); + /// + /// let (prefix, suffix) = NestedTupleSplit::::nested_split(neple!(1_u8, 2_u16, 3_u32)); + /// assert_eq!((prefix, suffix), (neple!(1_u8, 2_u16), neple!(3_u32))); + /// ``` + /// + /// ```compile_fail + /// use tuplities::NestedTupleSplit; + /// use typenum::U3; + /// + /// let _ = NestedTupleSplit::::nested_split((1_u8, (2_u16, ()))); + /// ``` + /// + /// ```compile_fail + /// use tuplities::{neple, NestedTupleSplit}; + /// use typenum::U2; + /// + /// let tail = neple!(1_u8, 2_u16); + /// let _ = NestedTupleSplit::::nested_split((0_u8, &tail)); + /// ``` + fn nested_split(self) -> (Self::Prefix, Self::Suffix); +} + +/// Inserts at `typenum` indices through `NestedTuple::LEN`, using singleton termination at an empty tail. +pub trait NestedTupleInsert: NestedTuple { + /// The list type with `Item` inserted at `Idx`. + type Output: NestedTuple; + + /// Consumes the list, returning the list with `Item` inserted at `Idx`. + /// + /// # Examples + /// + /// ``` + /// use tuplities::{neple, NestedTupleInsert}; + /// use typenum::{U0, U1}; + /// + /// assert_eq!(NestedTupleInsert::::nested_insert((), 7_u8), (7_u8,)); + /// + /// let inserted = NestedTupleInsert::::nested_insert(neple!(1_u8, 2_u16), 3_u32); + /// assert_eq!(inserted, neple!(1_u8, 3_u32, 2_u16)); + /// ``` + /// + /// ```compile_fail + /// use tuplities::NestedTupleInsert; + /// use typenum::U3; + /// + /// let _ = NestedTupleInsert::::nested_insert((1_u8, (2_u16, ())), 3_u32); + /// ``` + /// + /// ```compile_fail + /// use tuplities::{neple, NestedTupleInsert}; + /// use typenum::U2; + /// + /// let tail = neple!(1_u8, 2_u16); + /// let _ = NestedTupleInsert::::nested_insert((0_u8, &tail), 9_u16); + /// ``` + fn nested_insert(self, item: Item) -> Self::Output; +} + +/// Removes at `typenum` indices below `NestedTuple::LEN`, singleton-terminating rebuilt prefixes when the remainder becomes empty. +pub trait NestedTupleRemove: NestedTuple { + /// The type of the removed head. + type Removed; + + /// The rebuilt list holding the remaining heads. + type Remainder: NestedTuple; + + /// Consumes the list, returning the removed head and the remainder. + /// + /// # Examples + /// + /// ``` + /// use tuplities::{neple, NestedTupleRemove}; + /// use typenum::{U0, U2}; + /// + /// assert_eq!(NestedTupleRemove::::nested_remove(neple!(7_u8)), (7_u8, ())); + /// + /// let (removed, remainder) = + /// NestedTupleRemove::::nested_remove(neple!(1_u8, 2_u16, 3_u32)); + /// assert_eq!((removed, remainder), (3_u32, neple!(1_u8, 2_u16))); + /// ``` + /// + /// ```compile_fail + /// use tuplities::NestedTupleRemove; + /// use typenum::U2; + /// + /// let _ = NestedTupleRemove::::nested_remove((1_u8, (2_u16, ()))); + /// ``` + /// + /// ```compile_fail + /// use tuplities::NestedTupleRemove; + /// use typenum::U0; + /// + /// let _ = NestedTupleRemove::::nested_remove(()); + /// ``` + /// + /// ```compile_fail + /// use tuplities::{neple, NestedTupleRemove}; + /// use typenum::U1; + /// + /// let tail = neple!(1_u8, 2_u16); + /// let _ = NestedTupleRemove::::nested_remove((0_u8, &tail)); + /// ``` + fn nested_remove(self) -> (Self::Removed, Self::Remainder); +} + +impl NestedTupleSplit for T { + type Prefix = (); + type Suffix = Self; + + #[inline] + fn nested_split(self) -> (Self::Prefix, Self::Suffix) { + ((), self) + } +} + +impl NestedTupleSplit for (Head,) { + type Prefix = (Head,); + type Suffix = (); + + #[inline] + fn nested_split(self) -> (Self::Prefix, Self::Suffix) { + ((self.0,), ()) + } +} + +impl NestedTupleSplit> for (Head, Tail) +where + typenum::UInt: core::ops::Sub, + Tail: NestedTupleSplit>>, + >>>::Prefix: + NestedTuplePushFront, +{ + type Prefix = <>>>::Prefix + as NestedTuplePushFront>::Output; + type Suffix = >>>::Suffix; + + #[inline] + fn nested_split(self) -> (Self::Prefix, Self::Suffix) { + let (head, tail) = self; + let (prefix, suffix) = + >>>::nested_split(tail); + (prefix.nested_push_front(head), suffix) + } +} + +impl NestedTupleInsert for T +where + T: NestedTuple + NestedTuplePushFront, +{ + type Output = >::Output; + + #[inline] + fn nested_insert(self, item: Item) -> Self::Output { + self.nested_push_front(item) + } +} + +impl NestedTupleInsert for (Head,) { + type Output = (Head, (Item,)); + + #[inline] + fn nested_insert(self, item: Item) -> Self::Output { + (self.0, (item,)) + } +} + +impl NestedTupleInsert, Item> for (Head, Tail) +where + typenum::UInt: core::ops::Sub, + Tail: NestedTupleInsert>, Item>, +{ + type Output = ( + Head, + >, Item>>::Output, + ); + + #[inline] + fn nested_insert(self, item: Item) -> Self::Output { + let (head, tail) = self; + let inserted = + >, Item>>::nested_insert( + tail, item, + ); + (head, inserted) + } +} + +impl NestedTupleRemove for (Head,) { + type Removed = Head; + type Remainder = (); + + #[inline] + fn nested_remove(self) -> (Self::Removed, Self::Remainder) { + (self.0, ()) + } +} + +impl NestedTupleRemove for (Head, Tail) { + type Removed = Head; + type Remainder = Tail; + + #[inline] + fn nested_remove(self) -> (Self::Removed, Self::Remainder) { + (self.0, self.1) + } +} + +impl NestedTupleRemove> for (Head, Tail) +where + typenum::UInt: core::ops::Sub, + Tail: NestedTupleRemove>>, + >>>::Remainder: + NestedTuplePushFront, +{ + type Removed = >>>::Removed; + type Remainder = <>>>::Remainder + as NestedTuplePushFront>::Output; + + #[inline] + fn nested_remove(self) -> (Self::Removed, Self::Remainder) { + let (head, tail) = self; + let (removed, remainder) = + >>>::nested_remove(tail); + (removed, remainder.nested_push_front(head)) + } +} diff --git a/tuplities-flatten-nest/src/nested_try_from.rs b/tuplities/src/nested_try_from.rs similarity index 81% rename from tuplities-flatten-nest/src/nested_try_from.rs rename to tuplities/src/nested_try_from.rs index 7d647d3..7f98db8 100644 --- a/tuplities-flatten-nest/src/nested_try_from.rs +++ b/tuplities/src/nested_try_from.rs @@ -15,6 +15,23 @@ pub trait NestedTupleTryFrom: Sized { /// # Errors /// /// Returns an error of type `E` if any element conversion fails. + /// + /// # Examples + /// + /// ```rust + /// use core::num::TryFromIntError; + /// use tuplities::NestedTupleTryFrom; + /// + /// let source = (1_u8, (2_u8, (3_u8,))); + /// let result: Result<(u16, (u16, (u16,))), TryFromIntError> = + /// <(u16, (u16, (u16,)))>::nested_tuple_try_from(source); + /// assert_eq!(result, Ok((1_u16, (2, (3,))))); + /// + /// let source = (1_u16, (300_u16, (3_u16,))); + /// let result: Result<(u8, (u8, (u8,))), TryFromIntError> = + /// <(u8, (u8, (u8,)))>::nested_tuple_try_from(source); + /// assert!(matches!(result, Err(_))); + /// ``` fn nested_tuple_try_from(other: T) -> Result; } @@ -29,6 +46,23 @@ pub trait NestedTupleTryInto: Sized { /// # Errors /// /// Returns an error of type `E` if any element conversion fails. + /// + /// # Examples + /// + /// ```rust + /// use core::num::TryFromIntError; + /// use tuplities::NestedTupleTryInto; + /// + /// let source = (1_u8, (2_u8, (3_u8,))); + /// let result: Result<(u16, (u16, (u16,))), TryFromIntError> = + /// source.nested_tuple_try_into(); + /// assert_eq!(result, Ok((1_u16, (2, (3,))))); + /// + /// let source = (1_u16, (300_u16, (3_u16,))); + /// let result: Result<(u8, (u8, (u8,))), TryFromIntError> = + /// source.nested_tuple_try_into(); + /// assert!(matches!(result, Err(_))); + /// ``` fn nested_tuple_try_into(self) -> Result; } @@ -49,6 +83,16 @@ where /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait NestedTupleFrom: Sized { /// Converts `other` into `Self` by applying `From` element-wise. + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleFrom; + /// + /// let source = (1_u8, (2_u8, (3_u8,))); + /// let result: (u16, (u16, (u16,))) = <(u16, (u16, (u16,)))>::nested_tuple_from(source); + /// assert_eq!(result, (1_u16, (2, (3,)))); + /// ``` fn nested_tuple_from(other: T) -> Self; } @@ -59,12 +103,22 @@ pub trait NestedTupleFrom: Sized { /// Part of the [`tuplities`](https://docs.rs/tuplities/latest/tuplities/) crate. pub trait NestedTupleInto: Sized { /// Converts `self` into `T` by applying `Into` element-wise. + /// + /// # Examples + /// + /// ```rust + /// use tuplities::NestedTupleInto; + /// + /// let source = (1_u8, (2_u8, (3_u8,))); + /// let result: (u16, (u16, (u16,))) = source.nested_tuple_into(); + /// assert_eq!(result, (1_u16, (2, (3,)))); + /// ``` fn nested_tuple_into(self) -> T; } impl NestedTupleInto<()> for () { #[inline] - fn nested_tuple_into(self) -> () {} + fn nested_tuple_into(self) {} } impl NestedTupleInto<(OtherHead,)> for (Head,) diff --git a/tuplities/src/nested_tuple.rs b/tuplities/src/nested_tuple.rs new file mode 100644 index 0000000..465c554 --- /dev/null +++ b/tuplities/src/nested_tuple.rs @@ -0,0 +1,88 @@ +//! Structural membership, length, and construction for recursive tuples. + +/// A recursive list with unit or singleton termination and an element count. +/// +/// ``` +/// use tuplities::NestedTuple; +/// +/// assert_eq!(<()>::LEN, 0); +/// assert_eq!(<(u8, (u16,))>::LEN, 2); +/// assert_eq!(<(u8, (u16, ()))>::LEN, 2); +/// ``` +/// +/// Scalar tails do not form recursive lists. +/// +/// ```compile_fail +/// use tuplities::NestedTuple; +/// +/// fn length(_: T) -> usize { T::LEN } +/// let _ = length((1_u8, 2_u8)); +/// ``` +pub trait NestedTuple { + /// The number of heads in the recursive list. + const LEN: usize; +} + +impl NestedTuple for () { + const LEN: usize = 0; +} + +impl NestedTuple for (Head,) { + const LEN: usize = 1; +} + +impl NestedTuple for (Head, Tail) { + const LEN: usize = 1 + Tail::LEN; +} + +impl NestedTuple for &T { + const LEN: usize = T::LEN; +} + +impl NestedTuple for &mut T { + const LEN: usize = T::LEN; +} + +/// Constructs a singleton-terminated recursive tuple, evaluating each expression once from left to right. +/// +/// ``` +/// use tuplities::neple; +/// +/// let values = neple!(1_u8, "two", (true, 3_u16),); +/// assert_eq!(values, (1, ("two", ((true, 3),)))); +/// ``` +#[macro_export] +macro_rules! neple { + () => { + () + }; + ($head:expr $(,)?) => { + ($head,) + }; + ($head:expr, $($tail:expr),+ $(,)?) => { + ($head, $crate::neple!($($tail),+)) + }; +} + +/// Names the singleton-terminated recursive tuple type for the supplied element types. +/// +/// ``` +/// use tuplities::{neple, neplety, NestedTuple}; +/// +/// type Values = neplety!(u8, &'static str, (bool, u16),); +/// let values: Values = neple!(1, "two", (true, 3)); +/// assert_eq!(Values::LEN, 3); +/// assert_eq!(values, (1, ("two", ((true, 3),)))); +/// ``` +#[macro_export] +macro_rules! neplety { + () => { + () + }; + ($head:ty $(,)?) => { + ($head,) + }; + ($head:ty, $($tail:ty),+ $(,)?) => { + ($head, $crate::neplety!($($tail),+)) + }; +} diff --git a/tuplities/tests/construction_macros.rs b/tuplities/tests/construction_macros.rs new file mode 100644 index 0000000..d5a6a3d --- /dev/null +++ b/tuplities/tests/construction_macros.rs @@ -0,0 +1,53 @@ +//! Construction preserves element boundaries, ownership, and evaluation order. + +use tuplities::{NestedTuple, NestedTupleChain, neple as list, neplety as List}; + +#[derive(Debug, PartialEq)] +struct Owned(u8); + +#[test] +fn empty_and_singleton_inputs_have_canonical_termination() { + type Empty = List!(); + type Singleton = List!(Owned,); + assert_eq!(Empty::LEN, 0); + assert_eq!(Singleton::LEN, 1); + let original = Owned(7); + let values: Singleton = list!().chain(list!(original,)); + assert_eq!(values, (Owned(7),)); + let singleton: List!(Owned) = list!(Owned(8)); + assert_eq!(singleton, (Owned(8),)); +} + +#[test] +fn expressions_are_moved_once_in_left_to_right_order() { + let mut calls = 0; + let mut next = || { + calls += 1; + Owned(calls) + }; + let values = list!(next(), next(), next(),); + assert_eq!(values, (Owned(1), (Owned(2), (Owned(3),)))); + assert_eq!(calls, 3); +} + +#[test] +fn complex_types_and_tuple_heads_keep_element_boundaries() { + type Values = List!((u8, u16), Option>, [u8; 2],); + let values: Values = list!((1, 2), Some(Ok(3)), [4, 5]); + assert_eq!(values, ((1, 2), (Some(Ok(3)), ([4, 5],)))); + assert_eq!(Values::LEN, 3); +} + +#[test] +fn borrowed_heads_preserve_lifetimes_and_mutate_original_storage() { + fn borrow(bytes: &mut [u8; 2]) -> List!(&mut [u8; 2], fn(u8, u8) -> u8) { + list!(bytes, u8::wrapping_add as fn(u8, u8) -> u8) + } + + let mut bytes = [250, 10]; + { + let values = borrow(&mut bytes); + values.0[0] = values.1.0(values.0[0], values.0[1]); + } + assert_eq!(bytes, [4, 10]); +} diff --git a/tuplities/tests/flat_bridge_contracts.rs b/tuplities/tests/flat_bridge_contracts.rs new file mode 100644 index 0000000..4f737df --- /dev/null +++ b/tuplities/tests/flat_bridge_contracts.rs @@ -0,0 +1,44 @@ +//! Flat and nested conversions preserve values and borrows. + +use tuplities::{ + FlattenNestedTuple, FlattenNestedTupleMatrix, NestTuple, NestTupleMatrix, NestTupleMut, + NestTupleRef, +}; + +#[test] +fn owned_round_trip_preserves_tuple_valued_heads() { + let values = (String::from("head"), (3_u8, 5_u16), vec![7_u32, 11]); + let nested = values.nest(); + assert_eq!(nested.0, "head"); + assert_eq!(nested.1.0, (3, 5)); + assert_eq!(nested.1.1.0, vec![7, 11]); + let flat = nested.flatten(); + assert_eq!(flat, (String::from("head"), (3, 5), vec![7, 11])); +} + +#[test] +fn borrowed_bridges_keep_the_original_storage() { + let mut flat = (String::from("head"), vec![2_u8, 3], 5_u16); + let references = flat.nest_ref().flatten(); + assert!(core::ptr::eq(references.0, &raw const flat.0)); + assert!(core::ptr::eq(references.1, &raw const flat.1)); + { + let nested = flat.nest_mut(); + nested.0.push('!'); + nested.1.0.push(7); + *nested.1.1.0 = 11; + } + assert_eq!(flat, (String::from("head!"), vec![2, 3, 7], 11)); +} + +#[test] +fn matrix_round_trip_preserves_rows_and_columns() { + let matrix = ((String::from("a"), 2_u8), (String::from("b"), 3_u8)); + let nested = matrix.nest_matrix(); + assert_eq!(nested.0.0, "a"); + assert_eq!(nested.0.1.0, 2); + assert_eq!(nested.1.0.0, "b"); + assert_eq!(nested.1.0.1.0, 3); + let flat = nested.flatten_matrix(); + assert_eq!(flat, ((String::from("a"), 2), (String::from("b"), 3))); +} diff --git a/tuplities/tests/flat_bridge_sizes.rs b/tuplities/tests/flat_bridge_sizes.rs new file mode 100644 index 0000000..f08663f --- /dev/null +++ b/tuplities/tests/flat_bridge_sizes.rs @@ -0,0 +1,66 @@ +//! Flat bridge boundaries preserve element order and borrowing. +#![recursion_limit = "256"] + +use tuplities::{FlattenNestedTuple, NestTuple, NestTupleMut, NestTupleRef}; + +macro_rules! nested_values { + ($head:expr) => { ($head,) }; + ($head:expr, $($tail:expr),+) => { ($head, nested_values!($($tail),+)) }; +} + +macro_rules! boundary_contract { + ($name:ident; $($index:tt),+) => { + #[test] + fn $name() { + let mut flat = ($($index + 1_000_usize,)+); + { + let references = flat.nest_mut().flatten(); + $(*references.$index += 1_000;)+ + } + let nested = flat.nest(); + assert_eq!(nested, nested_values!($($index + 2_000_usize),+)); + let flat = nested.flatten(); + $(assert_eq!(flat.$index, $index + 2_000);)+ + let references = flat.nest_ref().flatten(); + $(assert!(core::ptr::eq(references.$index, &flat.$index));)+ + } + }; +} + +boundary_contract!(width_8; 0, 1, 2, 3, 4, 5, 6, 7); + +#[cfg(any( + feature = "size-16", + feature = "size-32", + feature = "size-48", + feature = "size-64", + feature = "size-96", + feature = "size-128" +))] +boundary_contract!(width_16; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + +#[cfg(any( + feature = "size-32", + feature = "size-48", + feature = "size-64", + feature = "size-96", + feature = "size-128" +))] +boundary_contract!(width_32; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31); + +#[cfg(any( + feature = "size-48", + feature = "size-64", + feature = "size-96", + feature = "size-128" +))] +boundary_contract!(width_48; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47); + +#[cfg(any(feature = "size-64", feature = "size-96", feature = "size-128"))] +boundary_contract!(width_64; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63); + +#[cfg(any(feature = "size-96", feature = "size-128"))] +boundary_contract!(width_96; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95); + +#[cfg(feature = "size-128")] +boundary_contract!(width_128; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127); diff --git a/tuplities/tests/recursive_index.rs b/tuplities/tests/recursive_index.rs new file mode 100644 index 0000000..eb7d905 --- /dev/null +++ b/tuplities/tests/recursive_index.rs @@ -0,0 +1,122 @@ +//! Recursive indexing is independent of flat tuple conversion limits. + +use tuplities::{ + NestedTuple, NestedTupleIndex, NestedTupleIndexMut, NestedTupleRow, NestedTupleRowMut, +}; +use typenum::{U0, U1, U2, U4, U8, U9}; + +#[test] +fn recursive_index_exceeds_flat_limit() { + let mut values = (1_u8, (2, (3, (4, (5, (6, (7, (8, (9, (10,)))))))))); + assert_eq!(*NestedTupleIndex::::nested_index(&values), 9); + *NestedTupleIndexMut::::nested_index_mut(&mut values) = 11; + *NestedTupleIndexMut::::nested_index_mut(&mut values) = 55; + *NestedTupleIndexMut::::nested_index_mut(&mut values) = 100; + assert_eq!( + values, + (11, (2, (3, (4, (55, (6, (7, (8, (9, (100,)))))))))) + ); +} + +#[test] +fn length_counts_heads_and_both_termination_forms() { + fn length(_: &T) -> usize { + T::LEN + } + + assert_eq!(length(&()), 0); + assert_eq!(length(&(String::from("singleton"),)), 1); + let mut singleton_terminated = ((1_u8, 2_u16), (String::from("head"), (true,))); + assert_eq!(length(&singleton_terminated), 3); + assert_eq!(length(&&singleton_terminated), 3); + assert_eq!(length(&&mut singleton_terminated), 3); + assert_eq!( + length(&((1_u8, 2_u16), (String::from("head"), (true, ())))), + 3 + ); +} + +#[test] +fn unit_terminated_indices_preserve_unselected_elements() { + let mut values = (String::from("head"), (2_u16, (String::from("tail"), ()))); + assert_eq!(NestedTupleIndex::::nested_index(&values), "head"); + assert_eq!(*NestedTupleIndex::::nested_index(&values), 2); + assert_eq!(NestedTupleIndex::::nested_index(&values), "tail"); + NestedTupleIndexMut::::nested_index_mut(&mut values).push('!'); + assert_eq!( + values, + (String::from("head"), (2, (String::from("tail!"), ()))) + ); +} + +#[test] +fn indexing_borrowed_tails_mutates_original_storage() { + let mut tail = (String::from("middle"), (String::from("tail"), ())); + { + let mut values = (1_u8, &tail); + assert!(core::ptr::eq( + NestedTupleIndex::::nested_index(&values), + &raw const tail.0, + )); + assert_eq!(NestedTupleIndex::::nested_index(&values), "tail"); + *NestedTupleIndexMut::::nested_index_mut(&mut values) = 2; + assert_eq!(values.0, 2); + } + { + let mut values = (1_u8, &mut tail); + NestedTupleIndexMut::::nested_index_mut(&mut values).push('!'); + NestedTupleIndexMut::::nested_index_mut(&mut values).push('?'); + assert_eq!(*NestedTupleIndex::::nested_index(&values), 1); + } + assert_eq!(tail, (String::from("middle!"), (String::from("tail?"), ()))); +} + +#[test] +fn row_access_exceeds_flat_limit_for_both_inner_terminations() { + let first = (1_u8, (2, (3, (4, (5, (6, (7, (8, (9, (10,)))))))))); + let second = ( + 11_u16, + (12, (13, (14, (15, (16, (17, (18, (19, (20, ()))))))))), + ); + let mut matrix = (first, (second, ())); + let row = NestedTupleRow::::nested_tuple_row(&matrix); + assert_eq!(row, (&9, (&19, ()))); + { + let row = NestedTupleRowMut::::nested_tuple_row_mut(&mut matrix); + *row.0 = 99; + *row.1.0 = 199; + } + assert_eq!( + matrix, + ( + (1, (2, (3, (4, (5, (6, (7, (8, (99, (10,)))))))))), + ( + ( + 11, + (12, (13, (14, (15, (16, (17, (18, (199, (20, ()))))))))) + ), + () + ) + ), + ); +} + +#[test] +fn row_access_through_borrowed_outer_tail_updates_original_matrix() { + let mut tail = ((30_u16, (40_u16, ())), ()); + { + let mut matrix = ((10_u8, (20_u8, ())), &mut tail); + let row = NestedTupleRow::::nested_tuple_row(&matrix); + assert_eq!(row, (&20, (&40, ()))); + let row = NestedTupleRowMut::::nested_tuple_row_mut(&mut matrix); + *row.0 = 21; + *row.1.0 = 41; + assert_eq!(matrix.0, (10, (21, ()))); + } + assert_eq!(tail, ((30, (41, ())), ())); + let matrix = ((10_u8, (20_u8, ())), &tail); + assert_eq!( + NestedTupleRow::::nested_tuple_row(&matrix), + (&20, (&41, ())) + ); +} diff --git a/tuplities/tests/recursive_structure.rs b/tuplities/tests/recursive_structure.rs new file mode 100644 index 0000000..00d2bad --- /dev/null +++ b/tuplities/tests/recursive_structure.rs @@ -0,0 +1,216 @@ +//! Consuming operations preserve elements, ownership, and termination contracts. + +use core::cell::Cell; +use tuplities::prelude::*; +use typenum::{U0, U1, U2, U3}; + +#[test] +fn split_preserves_suffixes_and_normalizes_prefixes() { + assert_eq!(NestedTupleSplit::::nested_split(()), ((), ())); + let result: ((), neplety!(u8)) = NestedTupleSplit::::nested_split(neple!(7_u8)); + assert_eq!(result, ((), (7,))); + let result: (neplety!(u8), ()) = NestedTupleSplit::::nested_split(neple!(7_u8)); + assert_eq!(result, ((7,), ())); + let original = (1_u8, (2_u16, (3_u32, ()))); + let (prefix, suffix): (neplety!(u8), (u16, (u32, ()))) = + NestedTupleSplit::::nested_split(original); + assert_eq!((prefix, suffix), ((1,), (2, (3, ())))); + assert_eq!(prefix.chain(suffix), original); + let result: (neplety!(u8, u16), neplety!(u32)) = + NestedTupleSplit::::nested_split(neple!(1_u8, 2_u16, 3_u32)); + assert_eq!(result, ((1, (2,)), (3,))); + let result: (neplety!(u8, u16, u32), ()) = NestedTupleSplit::::nested_split(original); + assert_eq!(result, (neple!(1, 2, 3), ())); +} + +#[test] +fn insert_accepts_every_boundary_and_preserves_untouched_termination() { + assert_eq!(NestedTupleInsert::::nested_insert((), 7_u8), (7,)); + assert_eq!( + NestedTupleInsert::::nested_insert(neple!(2_u16), 1_u8), + neple!(1, 2) + ); + assert_eq!( + NestedTupleInsert::::nested_insert(neple!(1_u8), 2_u16), + neple!(1, 2) + ); + let original = (1_u8, (2_u16, (3_u32, ()))); + assert_eq!( + NestedTupleInsert::::nested_insert(original, false), + (false, original) + ); + assert_eq!( + NestedTupleInsert::::nested_insert(original, (4_u8, 5_u16)), + (1, ((4, 5), (2, (3, ())))) + ); + assert_eq!( + NestedTupleInsert::::nested_insert(original, false), + (1, (2, (false, (3, ())))) + ); + let appended: neplety!(u8, u16, u32, bool) = + NestedTupleInsert::::nested_insert(original, true); + assert_eq!(appended, neple!(1, 2, 3, true)); +} + +#[test] +fn remove_preserves_suffixes_and_normalizes_empty_remainders() { + assert_eq!( + NestedTupleRemove::::nested_remove(neple!(7_u8)), + (7, ()) + ); + assert_eq!(NestedTupleRemove::::nested_remove((7_u8, ())), (7, ())); + let original = (1_u8, (2_u16, (3_u32, ()))); + assert_eq!( + NestedTupleRemove::::nested_remove(original), + (1, (2, (3, ()))) + ); + assert_eq!( + NestedTupleRemove::::nested_remove(original), + (2, (1, (3, ()))) + ); + let result: (u32, neplety!(u8, u16)) = NestedTupleRemove::::nested_remove(original); + assert_eq!(result, (3, neple!(1, 2))); + assert_eq!( + NestedTupleRemove::::nested_remove(neple!((1_u8, 2_u16), true)), + ((1, 2), (true,)) + ); +} + +#[test] +fn reverse_normalizes_both_terminations_without_flattening_heads() { + assert_eq!(NestedTupleReverse::nested_reverse(neple!(7_u8)), (7,)); + assert_eq!(NestedTupleReverse::nested_reverse((7_u8, ())), (7,)); + let original = ((1_u8, 2_u16), (3_u32, (false, ()))); + let reversed: neplety!(bool, u32, (u8, u16)) = NestedTupleReverse::nested_reverse(original); + assert_eq!(reversed, neple!(false, 3, (1, 2))); + assert_eq!(reversed.nested_reverse(), neple!((1, 2), 3, false)); + assert_eq!( + NestedTupleReverse::nested_reverse(neple!(1_u8, 2_u16, 3_u32)), + neple!(3, 2, 1) + ); +} + +#[test] +fn borrowed_suffixes_remain_untouched_at_owned_boundaries() { + let tail = neple!(String::from("tail"), false); + let (prefix, suffix) = NestedTupleSplit::::nested_split((1_u8, &tail)); + assert_eq!(prefix, (1,)); + assert!(core::ptr::eq(suffix, &raw const tail)); + let inserted = NestedTupleInsert::::nested_insert((1_u8, &tail), 2_u16); + assert_eq!((inserted.0, inserted.1.0), (1, 2)); + assert!(core::ptr::eq(inserted.1.1, &raw const tail)); + let (removed, remainder) = NestedTupleRemove::::nested_remove((1_u8, &tail)); + assert_eq!(removed, 1); + assert!(core::ptr::eq(remainder, &raw const tail)); + let empty = (); + let result: (_, (u8,)) = NestedTupleRemove::::nested_remove((1_u8, (2_u16, &empty))); + assert_eq!(result, (2, (1,))); + assert_eq!(NestedTupleReverse::nested_reverse((1_u8, &empty)), (1,)); +} + +#[test] +fn reference_elements_use_the_same_consuming_operations() { + let mut values = neple!(String::from("a"), String::from("b"), String::from("c")); + let mut extra = String::from("new"); + { + let (prefix, suffix) = NestedTupleSplit::::nested_split(values.nested_tuple_mut()); + let joined = prefix.chain(suffix); + let inserted = NestedTupleInsert::::nested_insert(joined, &mut extra); + let (removed, remainder) = NestedTupleRemove::::nested_remove(inserted); + removed.push('?'); + let reversed = remainder.nested_reverse(); + reversed.0.push('#'); + reversed.1.0.push('+'); + reversed.1.1.0.push('!'); + } + assert_eq!( + ( + values.0.as_str(), + values.1.0.as_str(), + values.1.1.0.as_str(), + extra.as_str() + ), + ("a!", "b?", "c#", "new+") + ); + let shared = values.nested_tuple_ref().nested_reverse(); + assert_eq!( + ( + shared.0.as_str(), + shared.1.0.as_str(), + shared.1.1.0.as_str() + ), + ("c#", "b?", "a!") + ); +} + +#[derive(Debug)] +struct Tracked<'a> { + id: u8, + drops: &'a Cell, +} + +impl Drop for Tracked<'_> { + fn drop(&mut self) { + self.drops.set(self.drops.get().saturating_add(1)); + } +} + +#[test] +fn all_operations_transfer_noncopy_elements_and_drop_each_once() { + let drops: [Cell; 4] = core::array::from_fn(|_| Cell::new(0)); + { + let source = neple!( + Tracked { + id: 0, + drops: &drops[0] + }, + Tracked { + id: 1, + drops: &drops[1] + }, + Tracked { + id: 2, + drops: &drops[2] + } + ); + let (prefix, suffix) = NestedTupleSplit::::nested_split(source); + let inserted = NestedTupleInsert::::nested_insert( + prefix.chain(suffix), + Tracked { + id: 3, + drops: &drops[3], + }, + ); + let (removed, remainder) = NestedTupleRemove::::nested_remove(inserted); + let reversed = remainder.nested_reverse(); + assert_eq!(removed.id, 1); + assert_eq!( + (reversed.0.id, reversed.1.0.id, reversed.1.1.0.id), + (2, 3, 0) + ); + assert_eq!(drops.each_ref().map(Cell::get), [0, 0, 0, 0]); + drop(removed); + assert_eq!(drops.each_ref().map(Cell::get), [0, 1, 0, 0]); + } + assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1, 1]); +} + +#[test] +fn mutable_borrowed_suffixes_preserve_storage_without_traversal() { + let mut tail = neple!(String::from("head"), String::from("tail")); + { + let (prefix, suffix) = NestedTupleSplit::::nested_split((1_u8, &mut tail)); + assert_eq!(prefix, (1,)); + suffix.0.push('!'); + let inserted = NestedTupleInsert::::nested_insert((prefix.0, suffix), 2_u16); + inserted.1.1.1.0.push('?'); + let (removed, remainder) = NestedTupleRemove::::nested_remove(inserted); + assert_eq!(removed, 2); + assert_eq!(remainder.0, 1); + } + assert_eq!((tail.0.as_str(), tail.1.0.as_str()), ("head!", "tail?")); + let mut empty = (); + let remainder: (u8,) = NestedTupleRemove::::nested_remove((1_u8, (2_u16, &mut empty))).1; + assert_eq!(remainder, (1,)); + assert_eq!(NestedTupleReverse::nested_reverse((3_u8, &mut empty)), (3,)); +} From 74d17bab717262f3b8e3ac9bb41168e7f214df37 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 4 Oct 2026 16:48:07 +0200 Subject: [PATCH 2/3] Remove custom verification tooling and fix feature cache keys --- .cargo/mutants-reviewed-equivalents.json | 39 -- .cargo/mutants.toml | 2 - .github/workflows/downstream.yml | 86 --- .github/workflows/rust.yml | 106 +-- .github/workflows/verification.yml | 49 -- .github/workflows/workflow-validation.yml | 10 +- .gitignore | 4 - CHANGELOG.md | 6 +- Cargo.toml | 4 - tools/check_mutants.py | 304 --------- tools/tests/test_check_mutants.py | 347 ---------- tools/tests/test_diesel_builders_revisions.py | 61 -- tools/tests/test_harness_syntax.py | 45 -- tools/tests/test_package_failures.py | 40 -- tools/verify_compile_workloads.sh | 349 ---------- tools/verify_diesel_builders.sh | 604 ------------------ tools/verify_harness_syntax.py | 48 -- tools/verify_package.sh | 448 ------------- 18 files changed, 16 insertions(+), 2536 deletions(-) delete mode 100644 .cargo/mutants-reviewed-equivalents.json delete mode 100644 .cargo/mutants.toml delete mode 100644 .github/workflows/downstream.yml delete mode 100644 .github/workflows/verification.yml delete mode 100644 tools/check_mutants.py delete mode 100644 tools/tests/test_check_mutants.py delete mode 100644 tools/tests/test_diesel_builders_revisions.py delete mode 100644 tools/tests/test_harness_syntax.py delete mode 100644 tools/tests/test_package_failures.py delete mode 100755 tools/verify_compile_workloads.sh delete mode 100755 tools/verify_diesel_builders.sh delete mode 100755 tools/verify_harness_syntax.py delete mode 100755 tools/verify_package.sh diff --git a/.cargo/mutants-reviewed-equivalents.json b/.cargo/mutants-reviewed-equivalents.json deleted file mode 100644 index 9f159bc..0000000 --- a/.cargo/mutants-reviewed-equivalents.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "records": [ - { - "file": "tuplities/src/nested_into_vec.rs", - "function": " for ()>::into_vec -> Vec", - "replacement": "vec![]", - "sha256": "da55257b29ace1b1e12422a5eb5a8a277a1759ea74201479e9e495c11b41acbd", - "reason": "Both constructors produce an empty `Vec` without allocating." - }, - { - "file": "tuplities/src/nested_option.rs", - "function": "::transpose -> Option", - "replacement": "Some(Default::default())", - "sha256": "7d55fedd4dcce4cb0a0265fa856b97f7d96a1e5cf083f3a482c46b98c6b4bef0", - "reason": "`Self::Transposed` is `()`, whose default is `()`." - }, - { - "file": "tuplities/src/nested_option.rs", - "function": " for ()>::transpose_or -> Result", - "replacement": "Ok(Default::default())", - "sha256": "7d55fedd4dcce4cb0a0265fa856b97f7d96a1e5cf083f3a482c46b98c6b4bef0", - "reason": "`Self::Transposed` is `()`, whose default is `()`." - }, - { - "file": "tuplities/src/nested_option_try_from.rs", - "function": " for ()>::nested_tuple_option_try_from -> Result", - "replacement": "Ok(Default::default())", - "sha256": "ef7a1d449e1e2a22109936cfe16e6db8fa0cde4dc39abd673d80e5a68dcf6bcf", - "reason": "`Self` is `()`, whose default is `()`." - }, - { - "file": "tuplities/src/nested_try_from.rs", - "function": " for ()>::nested_tuple_try_from -> Result", - "replacement": "Ok(Default::default())", - "sha256": "cbfe4acde1b8d7143001355215a8e30baca156a7a9c85ec1db0abecb965509dd", - "reason": "`Self` is `()`, whose default is `()`." - } - ] -} diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml deleted file mode 100644 index d59b17f..0000000 --- a/.cargo/mutants.toml +++ /dev/null @@ -1,2 +0,0 @@ -profile = "mutants" -gitignore = true diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml deleted file mode 100644 index 2b217b2..0000000 --- a/.github/workflows/downstream.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Downstream verification - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - schedule: - - cron: '0 4 * * 1' - workflow_dispatch: - -permissions: - contents: read - -jobs: - pinned-revisions: - name: Pinned revisions - if: github.event_name == 'push' || github.event_name == 'pull_request' - runs-on: ubuntu-latest - timeout-minutes: 90 - env: - CARGO_BUILD_JOBS: "4" - DIESEL_BUILDERS_REV: 7041ce661200fcf644d44c63e15695d880737945 - DIESEL_REV: 81f1cdb2927f6d0ca11039c16bca2ebd2c117be2 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: stable - - name: Install SQLite prerequisites - run: | - sudo apt-get update - sudo apt-get install --yes libsqlite3-dev pkg-config - - name: Cache cargo dependencies - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: | - ~/.cargo/git - ~/.cargo/registry - key: ${{ runner.os }}-downstream-cargo-pinned-${{ hashFiles('tools/verify_diesel_builders.sh') }} - - name: Verify the pinned diesel-builders and diesel revisions - run: bash tools/verify_diesel_builders.sh - - name: Upload diagnostics - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: downstream-diagnostics-pinned - path: target/verification/diesel-builders/ - - moving-main: - name: Moving git main - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - timeout-minutes: 90 - env: - CARGO_BUILD_JOBS: "4" - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: stable - - name: Install SQLite prerequisites - run: | - sudo apt-get update - sudo apt-get install --yes libsqlite3-dev pkg-config - - name: Cache cargo dependencies - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: | - ~/.cargo/git - ~/.cargo/registry - key: ${{ runner.os }}-downstream-cargo-moving-main - - name: Verify diesel-builders git main - run: bash tools/verify_diesel_builders.sh - - name: Upload diagnostics - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: downstream-diagnostics-moving-main - path: target/verification/diesel-builders/ diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b4c71de..8f565d9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -171,57 +171,23 @@ jobs: - name: Check embedded target run: cargo check -p tuplities --target thumbv7em-none-eabi --no-default-features - feature-configuration: - name: Feature configuration - if: github.event_name == 'push' || github.event_name == 'pull_request' - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - features: ${{ steps.discover.outputs.features }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Discover supported feature configurations - id: discover - run: | - python3 - <<'PY' - import json - import os - import tomllib - - with open("tuplities/Cargo.toml", "rb") as manifest: - features = tomllib.load(manifest)["features"] - - sizes = sorted( - (name for name in features if name.startswith("size-") and name[5:].isdigit()), - key=lambda name: int(name[5:]), - ) - configurations = [ - "--no-default-features --features alloc", - "--no-default-features --features flatten-nest", - ] - configurations += [ - f"--no-default-features --features flatten-nest,{size}" for size in sizes - ] - if len(sizes) >= 2: - configurations.append( - f"--no-default-features --features flatten-nest,{sizes[0]},{sizes[1]}" - ) - - with open(os.environ["GITHUB_OUTPUT"], "a") as output: - output.write(f"features={json.dumps(configurations)}") - PY - features: name: Features ${{ matrix.features }} if: github.event_name == 'push' || github.event_name == 'pull_request' - needs: feature-configuration runs-on: ubuntu-latest timeout-minutes: 45 strategy: matrix: - features: ${{ fromJSON(needs.feature-configuration.outputs.features) }} + features: + - alloc + - flatten-nest + - flatten-nest,size-16 + - flatten-nest,size-32 + - flatten-nest,size-48 + - flatten-nest,size-64 + - flatten-nest,size-96 + - flatten-nest,size-128 + - flatten-nest,size-16,size-32 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -244,13 +210,11 @@ jobs: uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: target - key: ${{ runner.os }}-cargo-build-features-stable-${{ matrix.features }}-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-cargo-build-features-stable-${{ strategy.job-index }}-${{ hashFiles('**/Cargo.lock') }} - name: Run tests env: - FEATURE_ARGS: ${{ matrix.features }} - run: | - read -r -a features <<< "$FEATURE_ARGS" - nice -n 10 cargo test -p tuplities "${features[@]}" + FEATURES: ${{ matrix.features }} + run: nice -n 10 cargo test -p tuplities --no-default-features --features "$FEATURES" coverage: name: Code Coverage @@ -296,47 +260,3 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} files: target/coverage.lcov fail_ci_if_error: false - - mutants: - name: Mutation testing - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - timeout-minutes: 180 - env: - CARGO_BUILD_JOBS: "2" - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: stable - - name: Cache cargo registry - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo index - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - name: Install cargo-mutants - uses: taiki-e/install-action@e407f7bafb71fd004bc5c2da3032e5470cbb6ef0 - with: - tool: cargo-mutants@27.1.0 - fallback: none - - name: Run bounded mutation testing - run: | - status=0 - nice -n 10 cargo mutants -p tuplities --jobs 2 --jobserver-tasks 2 --all-features \ - --output "$RUNNER_TEMP/mutation-results" || status=$? - python3 -B tools/check_mutants.py \ - "$RUNNER_TEMP/mutation-results/mutants.out/outcomes.json" --exit-code "$status" - - name: Upload mutation reports - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: mutation-reports - path: ${{ runner.temp }}/mutation-results/mutants.out/ diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml deleted file mode 100644 index 85b2f58..0000000 --- a/.github/workflows/verification.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Package Verification - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -env: - CARGO_TERM_COLOR: always - CARGO_BUILD_JOBS: "4" - -permissions: - contents: read - -jobs: - verification: - name: Verification - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust stable - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: stable - - name: Install Rust 1.85.0 - run: rustup toolchain install 1.85.0 - - name: Verify checkout compilation workloads - run: tools/verify_compile_workloads.sh - - name: Verify package contract - run: tools/verify_package.sh - - name: Upload verification artifacts - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: verification - path: | - target/verification/ - !target/verification/**/target/** - - name: Upload package archive - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: package-archive - path: target/package/*.crate diff --git a/.github/workflows/workflow-validation.yml b/.github/workflows/workflow-validation.yml index 6bb8c87..f7376ac 100644 --- a/.github/workflows/workflow-validation.yml +++ b/.github/workflows/workflow-validation.yml @@ -11,14 +11,14 @@ permissions: jobs: validate: - name: Workflow and harness validation + name: Workflow validation runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: Install security and shell validators + - name: Install workflow validators uses: taiki-e/install-action@e407f7bafb71fd004bc5c2da3032e5470cbb6ef0 # v2.87.24 with: tool: shellcheck@0.11.0,zizmor@1.30.1 @@ -34,9 +34,3 @@ jobs: run: '"$RUNNER_TEMP/actionlint"' - name: Audit workflow security run: zizmor --offline --strict-collection .github/workflows - - name: Check shell harnesses - run: shellcheck tools/*.sh - - name: Check Python syntax - run: python3 -B tools/verify_harness_syntax.py - - name: Test verification policies - run: python3 -B -m unittest discover -s tools/tests diff --git a/.gitignore b/.gitignore index 5c25314..76fcd9d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,10 +9,6 @@ target # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -# Generated by cargo mutants -# Contains mutation testing data -**/mutants.out*/ - # RustRover # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore diff --git a/CHANGELOG.md b/CHANGELOG.md index 82d12d3..905cb48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The minimum supported Rust version is `1.85`. - Public methods provide executable examples for result shapes, borrowed mutation, and conversion errors. - `cargo llvm-cov` measures runtime targets and doctests. -- CI covers Rust `1.85.0`, allocator-free embedded builds, supported flat widths, mixed features, and bounded mutation analysis. -- `tools/verify_diesel_builders.sh` exercises isolated downstream SQLite consumers. -- `tools/verify_compile_workloads.sh` retains first-build and second-build reports for recursive and flat workloads. -- CI validates workflows and harnesses, verifies packaged MSRV consumers, and exercises pinned and moving-main downstream dependencies. -- Mutation grading rejects new survivors, stale equivalence reviews, incomplete reports, and tool failures while retaining raw results. +- CI covers Rust `1.85.0`, allocator-free embedded builds, supported flat widths, and mixed features. - `unsafe_code` is forbidden. ## [0.1.4] - 2025-12-15 diff --git a/Cargo.toml b/Cargo.toml index ebad560..7151b9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,6 @@ rust-version = "1.85" [workspace.dependencies] typenum = { version = "1" } -[profile.mutants] -inherits = "test" -debug = "none" - [workspace.lints.rust] unsafe_code = "forbid" missing_docs = "forbid" diff --git a/tools/check_mutants.py b/tools/check_mutants.py deleted file mode 100644 index cf6562a..0000000 --- a/tools/check_mutants.py +++ /dev/null @@ -1,304 +0,0 @@ -"""Grade complete mutation runs against source-bound reviewed equivalents.""" - -import argparse -import hashlib -import json -import sys -from pathlib import Path - -REVIEWED_FILENAME = Path(".cargo") / "mutants-reviewed-equivalents.json" -ALLOWED_EXIT_CODES = (0, 2) -BASELINE_SCENARIO = "Baseline" -SUMMARY_FIELDS = { - "CaughtMutant": "caught", - "MissedMutant": "missed", - "Timeout": "timeout", - "Unviable": "unviable", -} -HEX_DIGITS = set("0123456789abcdef") -EXIT_OK = 0 -EXIT_FAILED = 1 - - -def _sha256(path): - return hashlib.sha256(path.read_bytes()).hexdigest() - - -def _is_count(value): - return isinstance(value, int) and not isinstance(value, bool) - - -def _is_hash(value): - return isinstance(value, str) and len(value) == 64 and set(value) <= HEX_DIGITS - - -def _function_label(mutant): - function = mutant.get("function") - if not isinstance(function, dict): - return None - name = function.get("function_name") - if not isinstance(name, str) or not name: - return None - return_type = function.get("return_type") - if isinstance(return_type, str) and return_type: - return f"{name} {return_type}" - return name - - -def _load_json(path, problems, label): - try: - raw = path.read_text(encoding="utf-8") - except OSError as exc: - problems.append(f"cannot read {label} {path}: {exc}") - return None - try: - data = json.loads(raw) - except json.JSONDecodeError as exc: - problems.append(f"corrupt {label} {path}: invalid JSON ({exc.msg} at line {exc.lineno} column {exc.colno})") - return None - if not isinstance(data, dict): - problems.append(f"corrupt {label} {path}: top level is not a JSON object") - return None - return data - - -def _grade_baseline(outcome, problems): - if outcome.get("summary") != "Success": - problems.append(f"baseline failed (summary {outcome.get('summary')!r}), so the run is not a normal complete run") - return - phases = outcome.get("phase_results") - if not isinstance(phases, list) or not phases: - problems.append("incomplete report: baseline has no phase results") - return - if [phase.get("phase") if isinstance(phase, dict) else None for phase in phases] != ["Build", "Test"]: - problems.append("incomplete report: baseline must complete Build and Test") - for phase in phases: - if not isinstance(phase, dict) or phase.get("process_status") != "Success": - name = phase.get("phase") if isinstance(phase, dict) else None - status = phase.get("process_status") if isinstance(phase, dict) else None - problems.append(f"baseline phase {name!r} did not succeed (status {status!r})") - -def _failed_status(status): - return ( - isinstance(status, dict) - and len(status) == 1 - and next(iter(status)) in ("Failure", "Signalled") - and _is_count(next(iter(status.values()))) - and next(iter(status.values())) > 0 - ) - - -def _grade_mutant_phases(outcome, name, problems): - summary = outcome.get("summary") - if summary not in ("MissedMutant", "CaughtMutant", "Unviable"): - return - phases = outcome.get("phase_results") - expected = ["Build"] if summary == "Unviable" else ["Build", "Test"] - if ( - not isinstance(phases, list) - or any(not isinstance(phase, dict) for phase in phases) - or [phase.get("phase") for phase in phases] != expected - ): - problems.append(f"incomplete phase results for mutant {name}") - return - statuses = [phase.get("process_status") for phase in phases] - if summary == "Unviable": - valid = _failed_status(statuses[0]) - elif summary == "CaughtMutant": - valid = statuses[0] == "Success" and _failed_status(statuses[1]) - else: - valid = statuses == ["Success", "Success"] - if not valid: - problems.append(f"phase results disagree with {summary} for mutant {name}") - - -def _grade_report(report, problems): - outcomes = report.get("outcomes") - if not isinstance(outcomes, list): - problems.append("incomplete report: outcomes is missing or not a list") - return None, [] - baselines = [] - mutants = [] - seen = set() - for index, outcome in enumerate(outcomes): - if not isinstance(outcome, dict): - problems.append(f"malformed outcome {index}: not a JSON object") - continue - if outcome.get("scenario") == BASELINE_SCENARIO: - baselines.append(outcome) - continue - scenario = outcome.get("scenario") - if not isinstance(scenario, dict) or not isinstance(scenario.get("Mutant"), dict): - problems.append(f"malformed outcome {index}: unrecognized scenario {scenario!r}") - continue - mutant = scenario["Mutant"] - name = mutant.get("name") - if not isinstance(name, str) or not name: - problems.append(f"malformed outcome {index}: mutant name is missing") - continue - file = mutant.get("file") - label = _function_label(mutant) - replacement = mutant.get("replacement") - if ( - not isinstance(file, str) or not file - or "function" not in mutant - or (mutant["function"] is not None and label is None) - or not isinstance(replacement, str) - ): - problems.append(f"malformed mutant {name}: invalid file, function or replacement") - continue - if name in seen: - problems.append(f"duplicate outcome: {name}") - continue - seen.add(name) - _grade_mutant_phases(outcome, name, problems) - mutants.append((name, file, label, replacement, outcome.get("summary"))) - if len(baselines) != 1: - problems.append(f"incomplete report: {len(baselines)} baseline outcome(s) found, expected exactly 1") - else: - _grade_baseline(baselines[0], problems) - - counts = {field: 0 for field in SUMMARY_FIELDS.values()} - for name, _, _, _, summary in mutants: - field = SUMMARY_FIELDS.get(summary) - if field is None: - problems.append(f"unrecognized summary {summary!r} for mutant {name}") - continue - counts[field] += 1 - - for field in ("missed", "caught", "timeout", "unviable"): - reported = report.get(field) - if not _is_count(reported): - problems.append(f"corrupt report: {field} total is missing or not an integer") - elif reported != counts[field]: - problems.append(f"totals disagree: {field} is {reported} but {counts[field]} mutant outcome(s) have that summary") - total = report.get("total_mutants") - if not _is_count(total): - problems.append("corrupt report: total_mutants is missing or not an integer") - else: - if total != len(mutants): - problems.append(f"totals disagree: total_mutants is {total} but {len(mutants)} mutant outcome(s) are present") - if total != sum(counts.values()): - problems.append(f"totals disagree: total_mutants is {total} but the recognized summaries sum to {sum(counts.values())}") - if counts["timeout"]: - problems.append(f"incomplete run: {counts['timeout']} mutant(s) timed out. Only complete runs are accepted") - - missed = [ - (name, file, label, replacement) - for name, file, label, replacement, summary in mutants - if summary == "MissedMutant" - ] - return counts, missed - - -def _grade_records(reviewed, repo_root, problems): - records = reviewed.get("records") - if not isinstance(records, list): - problems.append("corrupt reviewed records: records is missing or not a list") - return {} - index = {} - for position, record in enumerate(records): - if not isinstance(record, dict): - problems.append(f"malformed reviewed record {position}: not a JSON object") - continue - file = record.get("file") - function = record.get("function") - replacement = record.get("replacement") - digest = record.get("sha256") - reason = record.get("reason") - if ( - not isinstance(file, str) or not file - or not isinstance(function, str) or not function - or not isinstance(replacement, str) or not replacement - or not _is_hash(digest) - or not isinstance(reason, str) or not reason.strip() - ): - problems.append(f"malformed reviewed record {position}: file, function, replacement, sha256 or reason is missing") - continue - key = (file, function, replacement) - if key in index: - problems.append(f"duplicate reviewed record: {file} {function} {replacement}") - continue - source = repo_root / file - if not source.is_file(): - index[key] = (False, f"source file {file} is missing") - else: - fresh = _sha256(source) == digest - index[key] = (fresh, "source no longer matches the reviewed hash" if not fresh else None) - for key, (fresh, detail) in index.items(): - if not fresh: - problems.append(f"stale equivalence record: {key[0]} {key[1]} {key[2]} ({detail})") - return index - - -def _match_missed(missed, records, problems): - accepted = [] - for name, file, label, replacement in missed: - entry = records.get((file, label, replacement)) - if entry is None: - problems.append(f"unreviewed missed mutant: {name}") - elif entry[0]: - accepted.append(name) - else: - problems.append(f"missed mutant covered only by a stale record: {name}") - return accepted - - -def _parse_args(argv): - parser = argparse.ArgumentParser(description="Grade a cargo-mutants run against reviewed mutant equivalents.") - parser.add_argument("raw_outcomes", type=Path, help="cargo-mutants JSON outcomes report") - parser.add_argument("--exit-code", type=int, required=True, metavar="N", help="cargo-mutants exit code") - parser.add_argument( - "--reviewed", - type=Path, - default=None, - help=f"reviewed records JSON (default {REVIEWED_FILENAME} under the repo root)", - ) - parser.add_argument( - "--repo-root", - type=Path, - default=Path(__file__).resolve().parent.parent, - help="repo root that record file paths resolve against", - ) - return parser.parse_args(argv) - - -def main(argv=None): - args = _parse_args(argv) - repo_root = args.repo_root - reviewed_path = repo_root / REVIEWED_FILENAME if args.reviewed is None else repo_root / args.reviewed - problems = [] - if args.exit_code not in ALLOWED_EXIT_CODES: - problems.append(f"cargo-mutants exit code {args.exit_code} is not a normal complete run (expected 0 or 2)") - report = _load_json(args.raw_outcomes, problems, "mutation report") - reviewed = _load_json(reviewed_path, problems, "reviewed records") - records = _grade_records(reviewed, repo_root, problems) if reviewed is not None else None - counts = None - accepted = [] - if report is not None: - counts, missed = _grade_report(report, problems) - if counts is not None and args.exit_code in ALLOWED_EXIT_CODES: - expected_exit = 2 if counts["missed"] else 0 - if args.exit_code != expected_exit: - problems.append(f"cargo-mutants exit code {args.exit_code} disagrees with missed={counts['missed']}") - if records is not None: - accepted = _match_missed(missed, records, problems) - if problems: - print(f"mutant grading: FAILED ({len(problems)} problem(s))") - for problem in problems: - print(f" problem: {problem}") - else: - print("mutant grading: ok") - if counts is not None: - total = counts["missed"] + counts["caught"] + counts["timeout"] + counts["unviable"] - print( - f" total={total} caught={counts['caught']} unviable={counts['unviable']} " - f"timeout={counts['timeout']} missed={counts['missed']}" - ) - for name in accepted: - print(f" accepted: {name}") - return EXIT_OK if not problems else EXIT_FAILED - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tools/tests/test_check_mutants.py b/tools/tests/test_check_mutants.py deleted file mode 100644 index 7651700..0000000 --- a/tools/tests/test_check_mutants.py +++ /dev/null @@ -1,347 +0,0 @@ -import contextlib -import hashlib -import importlib.util -import io -import json -import tempfile -import unittest -from pathlib import Path - -TOOLS_DIR = Path(__file__).resolve().parent.parent -GRADER_PATH = TOOLS_DIR / "check_mutants.py" - -EQUIVALENTS = [ - ( - "tuplities/src/nested_into_vec.rs", - " for ()>::into_vec", - "-> Vec", - "vec![]", - ), - ( - "tuplities/src/nested_option.rs", - "::transpose", - "-> Option", - "Some(Default::default())", - ), - ( - "tuplities/src/nested_option.rs", - " for ()>::transpose_or", - "-> Result", - "Ok(Default::default())", - ), - ( - "tuplities/src/nested_option_try_from.rs", - " for ()>::nested_tuple_option_try_from", - "-> Result", - "Ok(Default::default())", - ), - ( - "tuplities/src/nested_try_from.rs", - " for ()>::nested_tuple_try_from", - "-> Result", - "Ok(Default::default())", - ), -] - -FRESH_KEYS = [ - ( - "tuplities/src/nested_into_vec.rs", - " for (T,)>::into_vec", - "-> Vec", - "vec![]", - ), - ( - "tuplities/src/nested_try_from.rs", - " for (Head,)>::nested_tuple_try_from", - "-> Result", - "Ok(Default::default())", - ), -] - -UNREVIEWED_KEY = ( - "tuplities/src/nested_option.rs", - ",)>::transpose", - "-> Option", - "Some(Default::default())", -) - -SUMMARY_FIELDS = { - "CaughtMutant": "caught", - "MissedMutant": "missed", - "Timeout": "timeout", - "Unviable": "unviable", -} - -SPAN = {"start": {"line": 1, "column": 1}, "end": {"line": 2, "column": 2}} - - -def _load_grader(): - spec = importlib.util.spec_from_file_location("check_mutants", GRADER_PATH) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -def _baseline(summary="Success", phases=None): - if phases is None: - phases = [ - {"phase": "Build", "duration": 1.0, "process_status": "Success", "argv": ["cargo", "test", "--no-run"]}, - {"phase": "Test", "duration": 1.0, "process_status": "Success", "argv": ["cargo", "test"]}, - ] - return { - "scenario": "Baseline", - "summary": summary, - "log_path": "log/baseline.log", - "diff_path": None, - "phase_results": phases, - } - - -def _mutant(file, function_name, return_type, replacement, summary, line=1): - name = f"{file}:{line}:1: replace {function_name} {return_type} with {replacement}" - phases = _baseline()["phase_results"] - if summary == "Unviable": - phases = [{**phases[0], "process_status": {"Failure": 101}}] - elif summary == "CaughtMutant": - phases[1]["process_status"] = {"Failure": 101} - return { - "scenario": { - "Mutant": { - "name": name, - "package": "tuplities", - "file": file, - "function": {"function_name": function_name, "return_type": return_type, "span": SPAN}, - "span": SPAN, - "replacement": replacement, - "genre": "FnValue", - } - }, - "summary": summary, - "log_path": f"log/mutant-{line}.log", - "diff_path": None, - "phase_results": phases, - } - - -def _report(outcomes): - totals = {field: 0 for field in SUMMARY_FIELDS.values()} - for outcome in outcomes: - if outcome["scenario"] == "Baseline": - continue - field = SUMMARY_FIELDS.get(outcome["summary"]) - if field is not None: - totals[field] += 1 - return { - "outcomes": outcomes, - "total_mutants": sum(totals.values()), - **totals, - "success": 0, - "start_time": "2026-10-03T23:17:21.057444822Z", - "end_time": "2026-10-03T23:21:03.596964341Z", - "cargo_mutants_version": "27.1.0", - } - - -def _make_repo(root): - """Create the source files for the reviewed records and a fresh review file.""" - for file, _, _, _ in EQUIVALENTS: - path = root / file - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text("fn fixture() -> u8 { 1 }\n", encoding="utf-8") - records = [] - for file, function_name, return_type, replacement in EQUIVALENTS: - records.append( - { - "file": file, - "function": f"{function_name} {return_type}", - "replacement": replacement, - "sha256": hashlib.sha256((root / file).read_bytes()).hexdigest(), - "reason": "the replacement builds the same value the unit-tuple impl returns", - } - ) - reviewed = root / "reviewed.json" - reviewed.write_text(json.dumps({"records": records}, indent=2), encoding="utf-8") - return reviewed - - -class GraderTests(unittest.TestCase): - def setUp(self): - self._tmp = tempfile.TemporaryDirectory() - self.addCleanup(self._tmp.cleanup) - self.root = Path(self._tmp.name) - self.reviewed = _make_repo(self.root) - self.raw = self.root / "outcomes.json" - self.grader = _load_grader() - - def write_outcomes(self, outcomes): - self.raw.write_text(json.dumps(_report(outcomes)), encoding="utf-8") - - def _run(self, exit_code=2): - argv = [str(self.raw), "--exit-code", str(exit_code), "--reviewed", str(self.reviewed), "--repo-root", str(self.root)] - buffer = io.StringIO() - with contextlib.redirect_stdout(buffer): - code = self.grader.main(argv) - return code, buffer.getvalue() - - def _accepted_outcomes(self): - outcomes = [_baseline()] - for line, key in enumerate(EQUIVALENTS, start=1): - outcomes.append(_mutant(*key, "MissedMutant", line)) - for line, key in enumerate(FRESH_KEYS, start=10): - outcomes.append(_mutant(*key, "CaughtMutant", line)) - for line, key in enumerate(FRESH_KEYS, start=20): - outcomes.append(_mutant(*key, "Unviable", line)) - return outcomes - - def test_known_equivalents_accepted(self): - self.write_outcomes(self._accepted_outcomes()) - code, out = self._run() - self.assertEqual(0, code) - - def test_fresh_missed_rejected(self): - outcomes = self._accepted_outcomes() - outcomes.append(_mutant(*UNREVIEWED_KEY, "MissedMutant", 30)) - self.write_outcomes(outcomes) - code, out = self._run() - self.assertNotEqual(0, code) - self.assertIn(UNREVIEWED_KEY[1], out) - - def test_changed_source_invalidates_acceptance(self): - self.write_outcomes(self._accepted_outcomes()) - path = self.root / "tuplities" / "src" / "nested_option.rs" - path.write_text(path.read_text(encoding="utf-8") + "// touched\n", encoding="utf-8") - code, out = self._run() - self.assertNotEqual(0, code) - - def test_timeout_rejected(self): - outcomes = [_baseline(), _mutant(*FRESH_KEYS[0], "Timeout", 1), _mutant(*FRESH_KEYS[1], "CaughtMutant", 2)] - self.write_outcomes(outcomes) - code, out = self._run(exit_code=2) - self.assertNotEqual(0, code) - - def test_baseline_failure_rejected(self): - failed = _baseline( - summary="Failure", - phases=[{"phase": "Build", "duration": 1.0, "process_status": "Failure", "argv": ["cargo", "test", "--no-run"]}], - ) - self.write_outcomes([failed, _mutant(*FRESH_KEYS[0], "CaughtMutant", 1)]) - code, out = self._run() - self.assertNotEqual(0, code) - - def test_tool_exit_code_rejected(self): - self.write_outcomes(self._accepted_outcomes()) - code, out = self._run(exit_code=1) - self.assertNotEqual(0, code) - - def test_corrupt_report_rejected(self): - self.raw.write_text("{ not json", encoding="utf-8") - code, out = self._run() - self.assertNotEqual(0, code) - - def test_incomplete_report_rejected(self): - self.write_outcomes(self._accepted_outcomes()) - data = json.loads(self.raw.read_text(encoding="utf-8")) - data["missed"] = data["missed"] - 1 - self.raw.write_text(json.dumps(data), encoding="utf-8") - code, out = self._run() - self.assertNotEqual(0, code) - - def test_missing_baseline_rejected(self): - self.write_outcomes(self._accepted_outcomes()) - data = json.loads(self.raw.read_text(encoding="utf-8")) - data["outcomes"] = data["outcomes"][1:] - self.raw.write_text(json.dumps(data), encoding="utf-8") - code, out = self._run() - self.assertNotEqual(0, code) - - def test_duplicate_reviewed_records_rejected(self): - self.write_outcomes(self._accepted_outcomes()) - data = json.loads(self.reviewed.read_text(encoding="utf-8")) - data["records"].append(dict(data["records"][0])) - self.reviewed.write_text(json.dumps(data), encoding="utf-8") - code, out = self._run() - self.assertNotEqual(0, code) - - def test_duplicate_outcome_rejected(self): - outcomes = self._accepted_outcomes() - duplicate = _mutant(*FRESH_KEYS[0], "CaughtMutant", 40) - outcomes.append(duplicate) - outcomes.append(duplicate) - self.write_outcomes(outcomes) - code, out = self._run() - self.assertNotEqual(0, code) - - def test_known_equivalent_absent_ok(self): - outcomes = [_baseline()] - for line, key in enumerate(FRESH_KEYS, start=1): - outcomes.append(_mutant(*key, "CaughtMutant", line)) - for line, key in enumerate(FRESH_KEYS, start=10): - outcomes.append(_mutant(*key, "Unviable", line)) - self.write_outcomes(outcomes) - code, out = self._run(exit_code=0) - self.assertEqual(0, code) - - def test_stale_record_without_survivor_rejected(self): - outcomes = [_baseline()] - for line, key in enumerate(FRESH_KEYS, start=1): - outcomes.append(_mutant(*key, "CaughtMutant", line)) - for line, key in enumerate(FRESH_KEYS, start=10): - outcomes.append(_mutant(*key, "Unviable", line)) - self.write_outcomes(outcomes) - path = self.root / "tuplities" / "src" / "nested_option.rs" - path.write_text(path.read_text(encoding="utf-8") + "// touched\n", encoding="utf-8") - code, out = self._run(exit_code=0) - self.assertNotEqual(0, code) - - def test_malformed_reviewed_record_rejected(self): - self.write_outcomes(self._accepted_outcomes()) - data = json.loads(self.reviewed.read_text(encoding="utf-8")) - data["records"][0]["reason"] = "" - data["records"][1]["sha256"] = "zzz" - self.reviewed.write_text(json.dumps(data), encoding="utf-8") - code, out = self._run() - self.assertNotEqual(0, code) - - def test_unrecognized_summary_rejected(self): - outcomes = [_baseline()] - for line, key in enumerate(FRESH_KEYS, start=1): - outcomes.append(_mutant(*key, "CaughtMutant", line)) - outcomes.append(_mutant(*UNREVIEWED_KEY, "Bogus", 30)) - self.write_outcomes(outcomes) - code, out = self._run() - self.assertNotEqual(0, code) - - def test_baseline_without_test_phase_rejected(self): - outcomes = self._accepted_outcomes() - outcomes[0]["phase_results"] = outcomes[0]["phase_results"][:1] - self.write_outcomes(outcomes) - code, _ = self._run() - self.assertNotEqual(0, code) - - def test_mutant_without_completed_phases_rejected(self): - outcomes = self._accepted_outcomes() - del outcomes[1]["phase_results"] - self.write_outcomes(outcomes) - code, _ = self._run() - self.assertNotEqual(0, code) - - def test_exit_code_disagrees_with_survivors_rejected(self): - self.write_outcomes(self._accepted_outcomes()) - code, _ = self._run(exit_code=0) - self.assertNotEqual(0, code) - - def test_caught_nonfunction_and_deletion_mutants_accepted(self): - operator = _mutant(*FRESH_KEYS[0], "CaughtMutant") - operator["scenario"]["Mutant"]["function"] = None - operator["scenario"]["Mutant"]["genre"] = "BinaryOperator" - operator["scenario"]["Mutant"]["replacement"] = "-" - deletion = _mutant(*FRESH_KEYS[1], "CaughtMutant") - deletion["scenario"]["Mutant"]["genre"] = "MatchArm" - deletion["scenario"]["Mutant"]["replacement"] = "" - self.write_outcomes([_baseline(), operator, deletion]) - code, _ = self._run(exit_code=0) - self.assertEqual(0, code) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/tests/test_diesel_builders_revisions.py b/tools/tests/test_diesel_builders_revisions.py deleted file mode 100644 index 423f3c1..0000000 --- a/tools/tests/test_diesel_builders_revisions.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import subprocess -import tempfile -import unittest -from pathlib import Path - - -REPO_ROOT = Path(__file__).resolve().parents[2] -SCRIPT = REPO_ROOT / "tools" / "verify_diesel_builders.sh" - -PINNED_BUILDERS_REV = "7041ce661200fcf644d44c63e15695d880737945" -PINNED_DIESEL_REV = "81f1cdb2927f6d0ca11039c16bca2ebd2c117be2" - - -class DieselBuildersRevisionTests(unittest.TestCase): - def run_script(self, env_updates): - directory = tempfile.TemporaryDirectory() - self.addCleanup(directory.cleanup) - env = dict(os.environ) - env["CARGO"] = str(Path(directory.name) / "absent-cargo") - env.pop("DIESEL_BUILDERS_REV", None) - env.pop("DIESEL_REV", None) - env.pop("TUPLITIES_PACKAGE_DIR", None) - env.update(env_updates) - proc = subprocess.run( - ["bash", str(SCRIPT)], - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - timeout=10, - ) - return proc - - def assert_rejected(self, env_updates): - proc = self.run_script(env_updates) - self.assertEqual(proc.returncode, 2, proc.stderr) - - def test_builders_revision_without_diesel_revision_is_rejected(self): - self.assert_rejected({"DIESEL_BUILDERS_REV": PINNED_BUILDERS_REV}) - - def test_diesel_revision_without_builders_revision_is_rejected(self): - self.assert_rejected({"DIESEL_REV": PINNED_DIESEL_REV}) - - def test_short_revision_is_rejected(self): - self.assert_rejected( - {"DIESEL_BUILDERS_REV": "abc123", "DIESEL_REV": PINNED_DIESEL_REV}, - ) - - def test_uppercase_revision_is_rejected(self): - self.assert_rejected( - { - "DIESEL_BUILDERS_REV": PINNED_BUILDERS_REV.upper(), - "DIESEL_REV": PINNED_DIESEL_REV, - }, - ) - - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/tests/test_harness_syntax.py b/tools/tests/test_harness_syntax.py deleted file mode 100644 index a567122..0000000 --- a/tools/tests/test_harness_syntax.py +++ /dev/null @@ -1,45 +0,0 @@ -import importlib.util -import tempfile -import unittest -from pathlib import Path - - -SPEC = importlib.util.spec_from_file_location( - "verify_harness_syntax", Path(__file__).parents[1] / "verify_harness_syntax.py" -) -MODULE = importlib.util.module_from_spec(SPEC) - - -class HarnessSyntaxTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - SPEC.loader.exec_module(MODULE) - - def check_source(self, source, suffix=".sh"): - with tempfile.TemporaryDirectory() as directory: - path = Path(directory) / ("harness" + suffix) - path.write_text(source) - MODULE.check(path) - - def test_valid_quoted_python_blocks(self): - self.check_source( - "python3 - <<'PY'\nvalue = 1\nPY\n" - 'python3 - <<"PYTHON"\nvalue = 2\nPYTHON\n' - ) - - def test_invalid_embedded_python_reports_original_line(self): - with self.assertRaises(SyntaxError) as failure: - self.check_source("#!/bin/sh\npython3 - <<'PY'\nif:\nPY\n") - self.assertEqual(failure.exception.lineno, 3) - - def test_unterminated_python_block_is_rejected(self): - with self.assertRaises(ValueError): - self.check_source("python3 - <<'PY'\nvalue = 1\n") - - def test_invalid_python_file_is_rejected(self): - with self.assertRaises(SyntaxError): - self.check_source("if:\n", ".py") - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/tests/test_package_failures.py b/tools/tests/test_package_failures.py deleted file mode 100644 index 0d2045e..0000000 --- a/tools/tests/test_package_failures.py +++ /dev/null @@ -1,40 +0,0 @@ -import json -import os -import subprocess -import tempfile -import unittest -from pathlib import Path - - -SCRIPT = Path(__file__).resolve().parents[1] / "verify_package.sh" - - -class PackageFailureTests(unittest.TestCase): - def test_missing_source_records_failed_step(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - source = root / "missing-source" - diagnostics = root / "diagnostics" - result = subprocess.run( - ["sh", str(SCRIPT), "--allow-dirty"], - env={ - **os.environ, - "TUPLITIES_PACKAGE_DIR": str(source), - "TUPLITIES_VERIFICATION_DIR": str(diagnostics), - }, - capture_output=True, - text=True, - timeout=15, - ) - self.assertEqual(result.returncode, 1) - summary_path, = diagnostics.glob("run-*/summary.json") - summary = json.loads(summary_path.read_text()) - self.assertIsNotNone(summary["failure"]) - self.assertEqual( - [(step["step"], step["status"]) for step in summary["steps"]], - [("checkout-manifest", "failed")], - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/verify_compile_workloads.sh b/tools/verify_compile_workloads.sh deleted file mode 100755 index afcbc6c..0000000 --- a/tools/verify_compile_workloads.sh +++ /dev/null @@ -1,349 +0,0 @@ -#!/bin/sh -set -eu - -root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) - -exec python3 -B - "$root" <<'PY' -import json -import os -import shlex -import shutil -import signal -import subprocess -import sys -import tempfile -import time -from datetime import datetime, timezone -from pathlib import Path - -ROOT = Path(sys.argv[1]).resolve() -BUILD_TIMEOUT = 1200 -META_TIMEOUT = 120 -RUN_TIMEOUT = 60 -VERSION_TIMEOUT = 30 - - -def resolve(path): - path = Path(path) - return path if path.is_absolute() else (ROOT / path).resolve() - - -PACKAGE_DIR = resolve(os.environ.get("TUPLITIES_PACKAGE_DIR", str(ROOT / "tuplities"))) -OUTPUT_ROOT = resolve( - os.environ.get( - "TUPLITIES_VERIFICATION_DIR", - str(ROOT / "target" / "verification" / "compile-workloads"), - ) -) -CARGO = shlex.split(os.environ.get("CARGO", "cargo")) - -CASES = [ - ("recursive8", 8, [], None), - ("recursive32", 32, [], None), - ("recursive64", 64, [], None), - ("recursive128", 128, [], None), - ("flat8", 8, ["flatten-nest"], 8), - ("flat32", 32, ["flatten-nest", "size-32"], 32), - ("flat128", 128, ["flatten-nest", "size-128"], 128), -] - - -class CaseFailure(Exception): - pass - - -def nested_literal(values): - out = f"({values[-1]},)" - for value in reversed(values[:-1]): - out = f"({value}, {out})" - return out - - -def generate_main(case, width, flat_width): - values = [1000 + 7 * i for i in range(width)] - option_values = [20000 + 13 * i for i in range(width)] - flat_values = [30000 + 11 * i for i in range(flat_width)] if flat_width else [] - middle = width // 2 - split_at = width // 2 - remove_at = width // 4 - insert_value = 900000 - mutated_value = 800000 - mutated = list(values) - mutated[middle] = mutated_value - inserted_values = values[:1] + [insert_value] + values[1:] - removed_values = values[:remove_at] + values[remove_at + 1:] - u32 = lambda count: ", ".join(["u32"] * count) - lines = [ - '#![recursion_limit = "512"]', - "use tuplities::prelude::*;", - "", - "fn main() {", - f" type List = neplety!({u32(width)});", - f" assert_eq!(List::LEN, {width});", - f" let mut list: List = neple!({', '.join(map(str, values))});", - "", - f" assert_eq!(*NestedTupleIndex::::nested_index(&list), {values[0]});", - f" assert_eq!(*NestedTupleIndex::::nested_index(&list), {values[middle]});", - f" assert_eq!(*NestedTupleIndex::::nested_index(&list), {values[-1]});", - "", - f" type Options = neplety!({', '.join(['Option'] * width)});", - f" let options: Options = neple!({', '.join(f'Some({v})' for v in option_values)});", - " let transposed: Option = options.transpose();", - f" assert_eq!(transposed, Some({nested_literal(option_values)}));", - "", - f" let (prefix, suffix): (neplety!({u32(split_at)}), neplety!({u32(width - split_at)})) =", - f" NestedTupleSplit::::nested_split(list);", - f" assert_eq!(prefix, {nested_literal(values[:split_at])});", - f" assert_eq!(suffix, {nested_literal(values[split_at:])});", - "", - f" let inserted = NestedTupleInsert::::nested_insert(list, {insert_value});", - f" assert_eq!(inserted, {nested_literal(inserted_values)});", - "", - f" let (removed, remainder) = NestedTupleRemove::::nested_remove(list);", - f" assert_eq!(removed, {values[remove_at]});", - f" assert_eq!(remainder, {nested_literal(removed_values)});", - "", - " let reversed = NestedTupleReverse::nested_reverse(list);", - f" assert_eq!(reversed, {nested_literal(values[::-1])});", - "", - f" *NestedTupleIndexMut::::nested_index_mut(&mut list) = {mutated_value};", - f" assert_eq!(*NestedTupleIndex::::nested_index(&list), {mutated_value});", - " let final_reversed = NestedTupleReverse::nested_reverse(list);", - f" assert_eq!(final_reversed, {nested_literal(mutated[::-1])});", - ] - scenarios = ( - "construction,indexing-shared,indexing-mutable,option-transpose," - "split,insert,remove,reverse" - ) - if flat_width: - flat_csv = ", ".join(map(str, flat_values)) - lines += [ - "", - f" let flat = ({flat_csv});", - " let round_trip = flat.nest().flatten();", - ] - lines += [f" assert_eq!(round_trip.{index}, {value});" for index, value in enumerate(flat_values)] - scenarios += f",flat-roundtrip-{flat_width}" - lines += [ - "", - f' println!("compile-workload case={case} width={width} scenarios={scenarios} status=ok");', - "}", - "", - ] - return "\n".join(lines), scenarios - - -def generate_manifest(case, package_dir, features): - extra = "" - if features: - extra = ", features = [" + ", ".join(f'"{feature}"' for feature in features) + "]" - return ( - "[package]\n" - f'name = "workload-{case}"\n' - 'version = "0.0.0"\n' - 'edition = "2021"\n' - "publish = false\n" - "\n" - "[workspace]\n" - "\n" - "[dependencies]\n" - f'tuplities = {{ path = "{package_dir}", default-features = false{extra} }}\n' - 'typenum = "1"\n' - ) - - -def execute(cmd, cwd, env, timeout): - command = [str(item) for item in cmd] - with subprocess.Popen(command, cwd=str(cwd), env=env, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, text=True, start_new_session=True) as process: - try: - out, err = process.communicate(timeout=timeout) - except subprocess.TimeoutExpired: - os.killpg(process.pid, signal.SIGKILL) - out, err = process.communicate(timeout=30) - raise subprocess.TimeoutExpired(command, timeout, output=out, stderr=err) from None - return subprocess.CompletedProcess(command, process.returncode, out, err) - - -def run_logged(cmd, cwd, env, log_path, identity, timeout): - started = datetime.now(timezone.utc).isoformat() - header = ( - f"identity={identity}\n" - f"command={shlex.join(map(str, cmd))}\n" - f"started={started}\n" - f"timeout={timeout}s\n" - f"cwd={cwd}\n" - "--- output ---\n" - ) - start = time.monotonic() - try: - completed = execute(cmd, cwd, env, timeout) - output = completed.stdout + completed.stderr - exit_code = completed.returncode - except subprocess.TimeoutExpired as exc: - output = "".join( - part - for part in (exc.stdout or "", exc.stderr or "") - if isinstance(part, str) - ) - exit_code = -1 - duration = time.monotonic() - start - with open(log_path, "a") as handle: - handle.write(header + output + "--- summary ---\n") - handle.write(f"duration={duration:.2f}s exit={exit_code}\n") - return exit_code - - -def capture(cmd, cwd, env, timeout): - completed = execute(cmd, cwd, env, timeout) - if completed.returncode != 0: - raise CaseFailure(f"command failed: {shlex.join(map(str, cmd))}\n{completed.stderr}") - return completed.stdout - - -def record_versions(case_dir, packages): - env = os.environ - cargo_version = capture(CARGO + ["--version"], case_dir, env, VERSION_TIMEOUT).strip() - rustc = ["rustc", "--version"] - if len(CARGO) > 1 and CARGO[1].startswith("+"): - rustc = ["rustup", "run", CARGO[1][1:], *rustc] - rustc_version = capture(rustc, case_dir, env, VERSION_TIMEOUT).strip() - lines = [ - f"cargo={cargo_version}", - f"rustc={rustc_version}", - ] - for name in sorted(packages): - package = packages[name] - lines.append( - f"package {name} version={package['version']} " - f"rust_version={package.get('rust_version') or 'unspecified'}" - ) - for key in ("CARGO", "RUSTC_WRAPPER", "CARGO_BUILD_RUSTC_WRAPPER"): - if key in env: - lines.append(f"{key}={env[key]}") - (case_dir / "versions.txt").write_text("\n".join(lines) + "\n") - - -def run_case(case, width, features, flat_width, run_dir, temp_base): - src_dir = temp_base / case - (src_dir / "src").mkdir(parents=True) - main_source, scenarios = generate_main(case, width, flat_width) - manifest = generate_manifest(case, PACKAGE_DIR, features) - (src_dir / "Cargo.toml").write_text(manifest) - (src_dir / "src" / "main.rs").write_text(main_source) - - case_dir = run_dir / case - case_dir.mkdir(parents=True) - (case_dir / "generated").mkdir() - shutil.copy2(src_dir / "Cargo.toml", case_dir / "generated" / "Cargo.toml") - shutil.copy2(src_dir / "src" / "main.rs", case_dir / "generated" / "main.rs") - target_dir = case_dir / "target" - env = os.environ - - (case_dir / "config.json").write_text( - json.dumps( - { - "case": case, - "width": width, - "features": features, - "flat_width": flat_width, - "package_dir": str(PACKAGE_DIR), - "scenarios": scenarios, - }, - indent=2, - ) - + "\n" - ) - - built_package_ids = set() - for build_index in (1, 2): - cmd = ["nice", "-n", "10", *CARGO, "build", "--release", "--timings", "--message-format=json-render-diagnostics", "-j", "4"] - if build_index == 2: - cmd.append("--locked") - build_env = {**env, "CARGO_TARGET_DIR": str(target_dir)} - identity = f"{run_dir.name}/{case}/build{build_index}" - exit_code = run_logged( - cmd, src_dir, build_env, case_dir / f"build{build_index}.log", identity, BUILD_TIMEOUT - ) - if exit_code != 0: - raise CaseFailure(f"build {build_index} exit={exit_code}, log={case_dir / f'build{build_index}.log'}") - shutil.copytree(target_dir / "cargo-timings", case_dir / "timings" / f"build{build_index}") - for line in (case_dir / f"build{build_index}.log").read_text().splitlines(): - if line.startswith("{"): - event = json.loads(line) - if event.get("reason") == "compiler-artifact": - built_package_ids.add(event["package_id"]) - - metadata_text = capture( - [*CARGO, "metadata", "--format-version", "1", "--frozen"], - src_dir, env, META_TIMEOUT - ) - metadata = json.loads(metadata_text) - (case_dir / "metadata.json").write_text(metadata_text) - packages = {package["name"]: package for package in metadata["packages"]} - expected_packages = {"workload-" + case, "tuplities", "typenum"} - if set(packages) != expected_packages: - raise CaseFailure(f"resolved packages {sorted(packages)} != {sorted(expected_packages)}") - - dependency_tree = capture([*CARGO, "tree", "--frozen"], src_dir, env, META_TIMEOUT) - feature_tree = capture([*CARGO, "tree", "--frozen", "-e", "features"], src_dir, env, META_TIMEOUT) - (case_dir / "dependency-tree.txt").write_text(dependency_tree) - (case_dir / "feature-tree.txt").write_text(feature_tree) - if features: - if "flatten-nest" not in feature_tree or "alloc" not in feature_tree: - raise CaseFailure("feature tree lacks flatten-nest/alloc") - if flat_width in (32, 128) and f"size-{flat_width}" not in feature_tree: - raise CaseFailure(f"feature tree lacks size-{flat_width}") - else: - for feature in ("flatten-nest", "alloc", "size-"): - if feature in feature_tree: - raise CaseFailure(f"recursive case activated feature {feature}") - - package_names = {package["id"]: package["name"] for package in metadata["packages"]} - unit_names = {package_names[package_id] for package_id in built_package_ids} - if unit_names != expected_packages: - raise CaseFailure(f"built packages {sorted(unit_names)} != {sorted(expected_packages)}") - (case_dir / "built-packages.json").write_text(json.dumps(sorted(unit_names)) + "\n") - - record_versions(case_dir, packages) - - executable = target_dir / "release" / f"workload-{case}" - expected_line = f"compile-workload case={case} width={width} scenarios={scenarios} status=ok" - exit_code = run_logged( - [executable], src_dir, env, case_dir / "run.log", f"{run_dir.name}/{case}/run", RUN_TIMEOUT - ) - if exit_code != 0: - raise CaseFailure(f"executable exit={exit_code}, log={case_dir / 'run.log'}") - stdout = (case_dir / "run.log").read_text().split("--- output ---\n", 1)[1] - stdout = stdout.split("--- summary ---\n", 1)[0].strip() - if stdout != expected_line: - raise CaseFailure(f"stdout {stdout!r} != expected {expected_line!r}") - - -def main(): - if not PACKAGE_DIR.is_dir(): - print(f"tuplities package dir missing: {PACKAGE_DIR}", file=sys.stderr) - return 1 - run_dir = OUTPUT_ROOT / (datetime.now(timezone.utc).strftime("run-%Y%m%d-%H%M%S-pid") + str(os.getpid())) - run_dir.mkdir(parents=True) - temp_base = Path(tempfile.mkdtemp(prefix="tuplities-compile-workloads-")) - failures = [] - try: - for case, width, features, flat_width in CASES: - try: - run_case(case, width, features, flat_width, run_dir, temp_base) - print(f"ok {case}") - except CaseFailure as failure: - failures.append(case) - print(f"FAILED {case}: {failure}", file=sys.stderr) - finally: - shutil.rmtree(temp_base) - print(f"run dir: {run_dir}") - if failures: - print(f"failed cases: {', '.join(failures)}", file=sys.stderr) - return 1 - return 0 - - -sys.exit(main()) -PY diff --git a/tools/verify_diesel_builders.sh b/tools/verify_diesel_builders.sh deleted file mode 100755 index 6a149bc..0000000 --- a/tools/verify_diesel_builders.sh +++ /dev/null @@ -1,604 +0,0 @@ -#!/usr/bin/env bash -# Verify diesel-builders at git main, or at an explicit paired revision, against the local tuplities checkout. -set -euo pipefail - -script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -root="$(cd -- "${script_dir}/.." && pwd)" -package_dir="${TUPLITIES_PACKAGE_DIR:-${root}/tuplities}" -cargo_bin="${CARGO:-cargo}" - -if [[ ! -f "${package_dir}/Cargo.toml" ]]; then - echo "error: tuplities manifest not found at ${package_dir}/Cargo.toml" >&2 - exit 1 -fi - -run_id="$(date -u +%Y%m%dT%H%M%SZ)-$$" -log_dir="${root}/target/verification/diesel-builders/${run_id}" -work_dir="$(mktemp -d "${TMPDIR:-/tmp}/tuplities-diesel-builders-verify.XXXXXX")" -cleanup() { rm -rf "${work_dir}"; } -trap cleanup EXIT -mkdir -p "${log_dir}" - -python3 - "${root}" "${package_dir}" "${work_dir}" "${log_dir}" "${cargo_bin}" "${run_id}" <<'PY' -import json -import os -import re -import signal -import subprocess -import sys -from datetime import datetime, timezone - -ROOT, PKG_DIR, WORK_DIR, LOG_DIR, CARGO, RUN_ID = sys.argv[1:7] - -# Subprocess timeouts in seconds. Every cargo invocation carries one. -TIMEOUTS = {"version": 30, "update": 900, "metadata": 120, "check": 1500, "run": 1500} - -# Paired revisions pin the builder source and the shared Diesel lockfile entry. -REV_ENV = { - "diesel-builders": "DIESEL_BUILDERS_REV", - "diesel": "DIESEL_REV", -} -REV_RE = re.compile(r"^[0-9a-f]{40}$") -SOURCE_URLS = { - "diesel-builders": "https://github.com/LucaCappelletti94/diesel-builders", - "diesel": "https://github.com/diesel-rs/diesel", -} - -# Two consumer configurations: the consumer's defaults, and no defaults with -# size-32. Each entry carries the extra feature options its configuration adds. -CONFIGS = [ - ("default", ""), - ("no-default-size32", 'default-features = false, features = ["size-32"]'), -] - -# Every scenario the smoke executable must reach. The harness fails if a -# marker is absent, so an omitted scenario cannot pass silently. -MARKERS = [ - "defaults", - "missing_email_error_identity", - "setters_insert_load", - "index_reads", - "sql_query_construction", - "find_after_mutation", - "upsert", - "ancestor_bundle", - "ancestor_read", - "delete", -] - - -ENV = {**os.environ, "CARGO_TERM_COLOR": "never"} - - -class StepFailure(Exception): - def __init__(self, message, log_path): - super().__init__(message) - self.log_path = log_path - - -class UsageError(Exception): - pass - - -def utcnow(): - return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - - -def write_log(path, text): - with open(path, "w") as fh: - fh.write(text) - - -def run_cargo(config, step, args, cwd, log_path): - cmd = ["nice", "-n", "10", CARGO, *args] - if step in ("check", "run"): - # --locked builds the exact resolution the metadata check saw. - cmd += ["-j", "4", "--locked"] - header = ( - f"run={RUN_ID} config={config} step={step} started={utcnow()}\n" - f"$ {' '.join(cmd)}\n" - ) - timeout = TIMEOUTS[step] - with subprocess.Popen( - cmd, - cwd=cwd, - env=ENV, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - start_new_session=True, - ) as proc: - try: - out, err = proc.communicate(timeout=timeout) - except subprocess.TimeoutExpired: - os.killpg(os.getpgid(proc.pid), signal.SIGKILL) - out, err = proc.communicate(timeout=30) - write_log( - log_path, - header + f"TIMED OUT after {timeout}s\n" + out + err, - ) - raise StepFailure(f"step {step} timed out after {timeout}s", log_path) from None - status = "ok" if proc.returncode == 0 else f"failed exit_code={proc.returncode}" - write_log(log_path, header + out + err + f"--- ended={utcnow()} status={status} ---\n") - if proc.returncode != 0: - raise StepFailure(f"step {step} failed with exit code {proc.returncode}", log_path) - return subprocess.CompletedProcess(cmd, proc.returncode, out, err) - - -def run_versioned(label, cmd): - proc = subprocess.run( - cmd, env=ENV, timeout=TIMEOUTS["version"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True, - ) - return f"{label} {proc.stdout.strip()}" - - -def requested_revisions(): - values = { - name: (os.environ.get(env) or "").strip() - for name, env in REV_ENV.items() - } - set_names = [name for name, value in values.items() if value] - if len(set_names) == 1: - raise UsageError( - f"{REV_ENV[set_names[0]]} is set without the other revision input; " - f"{REV_ENV['diesel-builders']} and {REV_ENV['diesel']} must be provided together" - ) - for name in set_names: - if not REV_RE.fullmatch(values[name]): - raise UsageError( - f"{REV_ENV[name]} must be a full 40-character lowercase git SHA, " - f"got {values[name]!r}" - ) - return values if set_names else {} - - -def source_ref(name, revisions): - if name == "diesel-builders" and revisions: - return f'rev = "{revisions[name]}"' - return 'branch = "main"' - - -def expected_source_prefix(name, revisions): - if name == "diesel-builders" and revisions: - return f"git+{SOURCE_URLS[name]}?rev={revisions[name]}#" - return f"git+{SOURCE_URLS[name]}?branch=main#" - - -def dependency_lines(revisions): - builders_url = SOURCE_URLS["diesel-builders"] - lines = {} - for config, extras in CONFIGS: - line = ( - f'diesel-builders = {{ git = "{builders_url}", ' - f"{source_ref('diesel-builders', revisions)}" - ) - if extras: - line += f", {extras}" - lines[config] = line + " }" - diesel = ( - f'diesel = {{ git = "{SOURCE_URLS["diesel"]}", ' - f"{source_ref('diesel', revisions)}, " - 'features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }' - ) - return lines, diesel - - -def check_metadata(meta, revisions): - by_name = {} - for package in meta["packages"]: - by_name.setdefault(package["name"], []).append(package) - - tuplities = by_name.get("tuplities") or [] - if len(tuplities) != 1: - raise StepFailure( - f"expected exactly one tuplities package in the graph, found {len(tuplities)}", - None, - ) - entry = tuplities[0] - expected = os.path.join(PKG_DIR, "Cargo.toml") - if entry["manifest_path"] != expected or entry.get("source") is not None: - raise StepFailure( - f"tuplities did not resolve to the local package {expected}, " - f"resolved to {entry['manifest_path']} (source={entry.get('source')})", - None, - ) - - resolved = {} - for name in SOURCE_URLS: - entries = by_name.get(name) or [] - if not entries: - raise StepFailure(f"{name} is missing from the resolved graph", None) - prefix = expected_source_prefix(name, revisions) - found = set() - for package in entries: - source = package.get("source") or "" - if not source.startswith(prefix): - raise StepFailure( - f"{name} resolved to the unexpected source {source!r}, expected " - f"{prefix} followed by the revision", - None, - ) - found.add(source.rsplit("#", 1)[-1]) - if len(found) != 1: - raise StepFailure( - f"{name} resolved to more than one revision, " - f"{', '.join(sorted(found))}", - None, - ) - resolved[name] = next(iter(found)) - if revisions and resolved[name] != revisions[name]: - raise StepFailure( - f"{name} resolved to {resolved[name]}, expected {revisions[name]}", - None, - ) - return resolved - - -def parse_markers(output): - return { - line[len("scen "):].strip() - for line in output.splitlines() - if line.startswith("scen ") - } - - -SMOKE_MAIN = r''' -use diesel::prelude::*; -use diesel_builders::prelude::*; -use diesel_builders::{BuilderError, IncompleteBuilderError}; - -/// User model for the `users` table, mirroring the consumer defaults fixture. -#[derive(Debug, Clone, Queryable, Selectable, Identifiable, PartialEq, TableModel)] -#[diesel(table_name = users)] -#[table_model(surrogate_key)] -pub struct User { - pub id: i32, - #[table_model(default = "Guest")] - pub name: String, - #[table_model(default = "User")] - pub role: String, - #[table_model(default = true)] - pub active: bool, - pub bio: Option, - pub email: String, -} - -/// Profile model inheriting from `users` with an inherited ancestor default. -#[derive(Debug, Clone, Queryable, Selectable, Identifiable, PartialEq, TableModel)] -#[diesel(table_name = profiles)] -#[table_model(ancestors(users))] -#[table_model(default(users::bio, "Default bio"))] -pub struct Profile { - pub id: i32, - pub location: String, -} - -fn establish_connection() -> SqliteConnection { - let mut conn = SqliteConnection::establish(":memory:").expect("in-memory SQLite connection"); - diesel::sql_query("PRAGMA foreign_keys = ON") - .execute(&mut conn) - .expect("foreign key pragma"); - conn -} - -fn create_tables(conn: &mut SqliteConnection) -> QueryResult<()> { - use diesel::RunQueryDsl; - diesel::sql_query( - "CREATE TABLE users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - role TEXT NOT NULL, - active BOOLEAN NOT NULL, - bio TEXT, - email TEXT NOT NULL - )", - ) - .execute(conn)?; - diesel::sql_query( - "CREATE TABLE profiles ( - id INTEGER PRIMARY KEY NOT NULL REFERENCES users(id) ON DELETE CASCADE, - location TEXT NOT NULL - )", - ) - .execute(conn)?; - Ok(()) -} - -fn scenario_defaults() { - let builder = users::table::builder(); - assert_eq!( - builder.may_get_column::(), - Some("Guest".to_string()) - ); - assert_eq!(builder.may_get_column::(), Some("User".to_string())); - assert_eq!(builder.may_get_column::(), Some(true)); - assert_eq!(builder.may_get_column::(), Some(None)); - assert_eq!(builder.may_get_column::(), None); - println!("scen defaults"); -} - -fn scenario_missing_email_error_identity(conn: &mut SqliteConnection) { - let builder = users::table::builder(); - let err = builder.clone().insert(conn).unwrap_err(); - assert!( - matches!( - err, - BuilderError::Incomplete(IncompleteBuilderError::MissingMandatoryField { - table_name: "users", - field_name: "email", - }) - ), - "expected the missing mandatory field identity, got: {err:?}" - ); - println!("scen missing_email_error_identity"); -} - -fn scenario_setters_insert_load(conn: &mut SqliteConnection) -> Result> { - let user = users::table::builder() - .try_name("Admin".to_string())? - .try_bio(Some("Bio".to_string()))? - .try_email("test@example.com".to_string())? - .insert(conn)?; - let loaded = User::find(user.id(), conn)?; - assert_eq!(loaded.id, user.id); - assert_eq!(loaded.name, "Admin"); - assert_eq!(loaded.role, "User"); - assert_eq!(loaded.active, true); - assert_eq!(loaded.bio, Some("Bio".to_string())); - assert_eq!(loaded.email, "test@example.com"); - assert_eq!(loaded, user); - println!("scen setters_insert_load"); - Ok(user) -} - -fn scenario_index_reads(user: &User) { - assert_eq!(*user.id(), user.id); - assert_eq!(user.name(), "Admin"); - assert_eq!(user.role(), "User"); - assert_eq!(user.active(), &true); - assert_eq!(user.bio(), &Some("Bio".to_string())); - println!("scen index_reads"); -} - -fn scenario_sql_query_construction( - conn: &mut SqliteConnection, - user: &User, -) -> Result<(), Box> { - let loaded = <(users::email,) as LoadFirst>::load_first( - ("test@example.com",), - conn, - )?; - assert_eq!(loaded, *user); - let many = <(users::email,) as LoadMany>::load_many( - ("test@example.com",), - conn, - )?; - assert_eq!(many, vec![user.clone()]); - println!("scen sql_query_construction"); - Ok(()) -} - -fn scenario_find_after_mutation( - conn: &mut SqliteConnection, - user: &mut User, -) -> Result<(), Box> { - let id = *user.id(); - user.name = "Changed name".to_string(); - let found = User::find(&id, conn)?; - assert_eq!(found.name, "Admin"); - assert_eq!(user.name, "Changed name"); - println!("scen find_after_mutation"); - Ok(()) -} - -fn scenario_upsert( - conn: &mut SqliteConnection, - user: &mut User, -) -> Result<(), Box> { - user.role = "Moderator".to_string(); - let upserted = user.upsert(conn)?; - assert_eq!(upserted.id, *user.id()); - assert_eq!(upserted.name, "Changed name"); - assert_eq!(upserted.role, "Moderator"); - assert_eq!(upserted.active, true); - assert_eq!(upserted.bio, Some("Bio".to_string())); - assert_eq!(upserted.email, "test@example.com"); - let loaded = User::find(user.id(), conn)?; - assert_eq!(loaded, upserted); - println!("scen upsert"); - Ok(()) -} - -fn scenario_ancestor_bundle( - conn: &mut SqliteConnection, -) -> Result<(Profile, User), Box> { - let builder = profiles::table::builder(); - assert_eq!( - builder.may_get_column::(), - Some(Some("Default bio".to_string())) - ); - assert_eq!( - builder.may_get_column::(), - Some("Guest".to_string()) - ); - assert_eq!(builder.may_get_column::(), None); - - let profile = builder - .try_email("profile@example.com".to_string())? - .location("Rome") - .insert(conn)?; - let profile_user = User::find(profile.id(), conn)?; - assert_eq!(profile_user.name, "Guest"); - assert_eq!(profile_user.role, "User"); - assert_eq!(profile_user.active, true); - assert_eq!(profile_user.bio, Some("Default bio".to_string())); - assert_eq!(profile_user.email, "profile@example.com"); - let loaded = Profile::find(profile.id(), conn)?; - assert_eq!(loaded.location, "Rome"); - assert_eq!(loaded, profile); - println!("scen ancestor_bundle"); - Ok((profile, profile_user)) -} - -fn scenario_ancestor_read( - conn: &mut SqliteConnection, - profile: &Profile, - profile_user: &User, -) -> Result<(), Box> { - let ancestor: User = profile.ancestor(conn)?; - assert_eq!(ancestor, *profile_user); - println!("scen ancestor_read"); - Ok(()) -} - -fn scenario_delete( - conn: &mut SqliteConnection, - profile: &Profile, - user_id: i32, -) -> Result<(), Box> { - let deleted = profile.delete(conn)?; - assert_eq!(deleted, 1); - assert!(!Profile::exists(profile.id(), conn)?); - assert!(!User::exists(profile.id(), conn)?); - assert!(User::exists(&user_id, conn)?); - println!("scen delete"); - Ok(()) -} - -fn main() -> Result<(), Box> { - let mut conn = establish_connection(); - create_tables(&mut conn)?; - - scenario_defaults(); - scenario_missing_email_error_identity(&mut conn); - let mut user = scenario_setters_insert_load(&mut conn)?; - scenario_index_reads(&user); - scenario_sql_query_construction(&mut conn, &user)?; - scenario_find_after_mutation(&mut conn, &mut user)?; - scenario_upsert(&mut conn, &mut user)?; - let (profile, profile_user) = scenario_ancestor_bundle(&mut conn)?; - scenario_ancestor_read(&mut conn, &profile, &profile_user)?; - scenario_delete(&mut conn, &profile, *user.id())?; - - println!("smoke complete"); - Ok(()) -} -''' - -CARGO_TOML = """\ -[package] -name = "diesel-builders-downstream" -version = "0.1.0" -edition = "2024" -publish = false - -[dependencies] -{dep_line} -{diesel_line} - -[workspace] - -[patch."https://github.com/LucaCappelletti94/tuplities"] -tuplities = {{ path = "{pkg_dir}" }} -""" - - -def materialize_project(project_dir, dep_line, diesel_line): - os.makedirs(os.path.join(project_dir, "src"), exist_ok=True) - write_log( - os.path.join(project_dir, "Cargo.toml"), - CARGO_TOML.format( - dep_line=dep_line, - diesel_line=diesel_line, - pkg_dir=PKG_DIR, - ), - ) - write_log(os.path.join(project_dir, "src", "main.rs"), SMOKE_MAIN) - - -def main(): - try: - revisions = requested_revisions() - except UsageError as exc: - print(f"error: {exc}", file=sys.stderr) - sys.exit(2) - mode = "pinned revisions" if revisions else "moving git main" - dep_lines, diesel_line = dependency_lines(revisions) - - rustc_version = run_versioned("rustc", ["rustc", "--version"]) - cargo_version = run_versioned("cargo", [CARGO, "--version"]) - - results = [] - status = "ok" - try: - for config, _extras in CONFIGS: - project_dir = os.path.join(WORK_DIR, config) - config_log_dir = os.path.join(LOG_DIR, config) - os.makedirs(config_log_dir, exist_ok=True) - materialize_project(project_dir, dep_lines[config], diesel_line) - try: - update_args = ["update"] - if revisions: - update_args += ["-p", "diesel", "--precise", revisions["diesel"]] - run_cargo( - config, "update", update_args, project_dir, - os.path.join(config_log_dir, "update.log"), - ) - meta_proc = run_cargo( - config, "metadata", ["metadata", "--format-version", "1"], project_dir, - os.path.join(config_log_dir, "metadata.log"), - ) - resolved = check_metadata(json.loads(meta_proc.stdout), revisions) - run_cargo( - config, "check", ["check", "--all-targets"], project_dir, - os.path.join(config_log_dir, "check.log"), - ) - run_proc = run_cargo( - config, "run", ["run", "--release"], project_dir, - os.path.join(config_log_dir, "run.log"), - ) - seen = parse_markers(run_proc.stdout) - missing = [marker for marker in MARKERS if marker not in seen] - if missing: - raise StepFailure( - f"scenarios omitted from the smoke run: {', '.join(missing)}", - os.path.join(config_log_dir, "run.log"), - ) - results.append((config, "ok", resolved)) - print(f"config {config}: ok") - except StepFailure as exc: - results.append((config, f"failed: {exc}", {})) - print(f"config {config}: FAILED ({exc})", file=sys.stderr) - if exc.log_path: - print(f"logs: {exc.log_path}", file=sys.stderr) - status = "failed" - break - finally: - lines = [ - f"run={RUN_ID} finished={utcnow()} status={status}", - rustc_version, - cargo_version, - f"mode: {mode}", - f"tuplities package: {os.path.join(PKG_DIR, 'Cargo.toml')}", - ] - if revisions: - lines.append( - "requested " - + " ".join(f"{name}={revisions[name]}" for name in SOURCE_URLS) - ) - for config, result, config_revisions in results: - lines.append( - f"config {config}: {result} " - f"diesel-builders={config_revisions.get('diesel-builders', 'unresolved')} " - f"diesel={config_revisions.get('diesel', 'unresolved')}" - ) - write_log(os.path.join(LOG_DIR, "run.log"), "\n".join(lines) + "\n") - - if status != "ok": - sys.exit(1) - print(f"all configurations verified ({mode}), logs under {LOG_DIR}") - - -main() -PY diff --git a/tools/verify_harness_syntax.py b/tools/verify_harness_syntax.py deleted file mode 100755 index 7283b01..0000000 --- a/tools/verify_harness_syntax.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -"""Validate Python files and quoted Python heredocs without executing them.""" - -import ast -import re -import sys -from pathlib import Path - - -HEADER = re.compile( - r"^[^\n]*\bpython(?:\d(?:\.\d+)?)?\b[^\n]*?<<\s*" - r"(?P['\"])(?PPY(?:THON)?)(?P=quote)[^\n]*\n", - re.MULTILINE, -) - - -def check(path): - source = path.read_text() - if path.suffix == ".py": - ast.parse(source, filename=str(path)) - return - for header in HEADER.finditer(source): - if header.group().lstrip().startswith("#"): - continue - closing = re.search( - rf"^{re.escape(header['delimiter'])}[ \t]*$", - source[header.end():], - re.MULTILINE, - ) - if closing is None: - raise ValueError(f"{path} has an unterminated Python heredoc") - body = source[header.end():header.end() + closing.start()] - padding = "\n" * source[:header.end()].count("\n") - ast.parse(padding + body, filename=str(path)) - - -def main(): - paths = [Path(argument) for argument in sys.argv[1:]] - if not paths: - tools = Path(__file__).resolve().parent - paths = sorted([*tools.rglob("*.py"), *tools.rglob("*.sh")]) - for path in paths: - check(path) - print(f"Python syntax verified in {len(paths)} harness files") - - -if __name__ == "__main__": - main() diff --git a/tools/verify_package.sh b/tools/verify_package.sh deleted file mode 100755 index e8af4b5..0000000 --- a/tools/verify_package.sh +++ /dev/null @@ -1,448 +0,0 @@ -#!/bin/sh -set -eu - -root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) - -usage() { - echo "usage: verify_package.sh [--allow-dirty]" -} - -allow_dirty=0 -for arg in "$@"; do - case "$arg" in - --allow-dirty) - allow_dirty=1 - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "unknown argument: $arg" >&2 - usage >&2 - exit 2 - ;; - esac -done - -if [ "$allow_dirty" = 1 ] || [ "${TUPLITIES_ALLOW_DIRTY:-0}" = 1 ]; then - allow_dirty=1 -else - allow_dirty=0 -fi - -exec python3 -B - "$root" "$allow_dirty" <<'PY' -import json -import os -import shlex -import shutil -import signal -import subprocess -import sys -import tempfile -import tarfile -import time -import tomllib -from datetime import datetime, timezone -from pathlib import Path - -ROOT = Path(sys.argv[1]).resolve() -ALLOW_DIRTY = sys.argv[2] == "1" - -PACKAGE_TIMEOUT = 300 -META_TIMEOUT = 120 -TEST_TIMEOUT = 600 -VERSION_TIMEOUT = 30 -WORKLOADS_TIMEOUT = 3000 - -BRIDGE_WIDTHS = [8, 16, 32, 48, 64, 96, 128] -DOCTEST_CASES = ( - ("default", []), - ("no-default", ["--no-default-features"]), -) - - -def resolve(path): - path = Path(path) - return path if path.is_absolute() else (ROOT / path).resolve() - - -PACKAGE_DIR = resolve(os.environ.get("TUPLITIES_PACKAGE_DIR", str(ROOT / "tuplities"))) -OUTPUT_ROOT = resolve( - os.environ.get( - "TUPLITIES_VERIFICATION_DIR", - str(ROOT / "target" / "verification" / "package"), - ) -) -CARGO = shlex.split(os.environ.get("CARGO", "cargo")) -MSRV_TOOLCHAIN = os.environ.get("TUPLITIES_MSRV_TOOLCHAIN", "1.85.0") -MSRV_CARGO = shlex.split(os.environ.get("TUPLITIES_MSRV_CARGO", f"cargo +{MSRV_TOOLCHAIN}")) - - -class StepFailure(Exception): - pass - - -def dependency_requirement(spec): - if isinstance(spec, str): - return spec - return spec.get("version", "*") - - -def execute(cmd, cwd, env, timeout): - command = [str(item) for item in cmd] - with subprocess.Popen(command, cwd=str(cwd), env=env, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, text=True, start_new_session=True) as process: - try: - out, err = process.communicate(timeout=timeout) - except subprocess.TimeoutExpired: - os.killpg(process.pid, signal.SIGKILL) - out, err = process.communicate(timeout=30) - raise subprocess.TimeoutExpired(command, timeout, output=out, stderr=err) from None - return subprocess.CompletedProcess(command, process.returncode, out, err) - - -def run_logged(cmd, cwd, env, log_path, identity, timeout): - started = datetime.now(timezone.utc).isoformat() - header = ( - f"identity={identity}\n" - f"command={shlex.join(map(str, cmd))}\n" - f"started={started}\n" - f"timeout={timeout}s\n" - f"cwd={cwd}\n" - "--- output ---\n" - ) - with open(log_path, "a") as handle: - handle.write(header) - start = time.monotonic() - try: - completed = execute(cmd, cwd, env, timeout) - output = completed.stdout + completed.stderr - exit_code = completed.returncode - stdout = completed.stdout - except subprocess.TimeoutExpired as exc: - stdout = exc.stdout if isinstance(exc.stdout, str) else "" - output = "".join( - part - for part in (exc.stdout or "", exc.stderr or "") - if isinstance(part, str) - ) - exit_code = -1 - duration = time.monotonic() - start - with open(log_path, "a") as handle: - handle.write(output + "--- summary ---\n") - handle.write(f"duration={duration:.2f}s exit={exit_code}\n") - return exit_code, stdout - - -def capture(cmd, cwd, env, timeout): - completed = execute(cmd, cwd, env, timeout) - if completed.returncode != 0: - raise StepFailure(f"command failed: {shlex.join(map(str, cmd))}\n{completed.stderr}") - return completed.stdout - - - -def checkout_facts(): - checkout_manifest = tomllib.loads((PACKAGE_DIR / "Cargo.toml").read_text()) - package = checkout_manifest["package"] - workspace = tomllib.loads((ROOT / "Cargo.toml").read_text()).get("workspace", {}) - workspace_package = workspace.get("package", {}) - - def value(field): - raw = package.get(field) - if isinstance(raw, dict) and raw.get("workspace"): - return workspace_package.get(field) - return raw - - typenum_spec = workspace.get("dependencies", {}).get("typenum") - if typenum_spec is None: - typenum_spec = checkout_manifest.get("dependencies", {}).get("typenum") - if typenum_spec is None: - raise StepFailure("typenum dependency not found in checkout manifest") - facts = { - "name": value("name"), - "version": value("version"), - "rust_version": value("rust-version"), - "typenum_requirement": dependency_requirement(typenum_spec), - } - if facts["name"] != "tuplities": - raise StepFailure(f"package name {facts['name']!r} is not 'tuplities'") - if not facts["version"] or not facts["rust_version"]: - raise StepFailure(f"checkout manifest missing version or rust-version: {facts}") - return facts - - -def inspect_archive(archive, run_dir, facts): - root_prefix = f"{facts['name']}-{facts['version']}" - members = [] - manifest_text = None - with tarfile.open(archive, "r:gz") as archive_file: - for member in sorted(archive_file.getmembers(), key=lambda item: item.name): - members.append(member.name) - if member.isfile() and member.name == f"{root_prefix}/Cargo.toml": - manifest_text = archive_file.extractfile(member).read().decode("utf-8") - (run_dir / "archive-file-list.txt").write_text("\n".join(members) + "\n") - for member in members: - if not member.startswith(root_prefix + "/"): - return f"archive member {member!r} outside root {root_prefix!r}" - parts = Path(member).parts[1:] - if "plans" in parts or any(part.startswith("tuplities-") for part in parts[:-1]): - return f"private plan or satellite package path in archive {member!r}" - manifests = [member for member in members if member.rsplit("/", 1)[-1] == "Cargo.toml"] - if manifests != [f"{root_prefix}/Cargo.toml"]: - return f"archive manifests {manifests} != [{root_prefix}/Cargo.toml]" - locks = [member for member in members if member.rsplit("/", 1)[-1] == "Cargo.lock"] - if locks != [f"{root_prefix}/Cargo.lock"]: - return f"archive locks {locks} != [{root_prefix}/Cargo.lock]" - if manifest_text is None: - return "archive manifest unreadable" - manifest = tomllib.loads(manifest_text) - package = manifest.get("package", {}) - if package.get("name") != facts["name"]: - return f"archive package name {package.get('name')!r} != {facts['name']!r}" - if package.get("version") != facts["version"]: - return f"archive version {package.get('version')!r} != {facts['version']!r}" - if package.get("rust-version") != facts["rust_version"]: - return f"archive rust-version {package.get('rust-version')!r} != {facts['rust_version']!r}" - dependencies = manifest.get("dependencies", {}) - if set(dependencies) != {"typenum"}: - return f"archive dependencies {sorted(dependencies)} != ['typenum']" - typenum_spec = dependencies["typenum"] - if isinstance(typenum_spec, dict) and typenum_spec.get("optional"): - return "typenum dependency is optional" - requirement = dependency_requirement(typenum_spec) - if requirement != facts["typenum_requirement"]: - return f"archive typenum requirement {requirement!r} != {facts['typenum_requirement']!r}" - normalized = { - "name": package["name"], - "version": package["version"], - "rust_version": package["rust-version"], - "dependencies": {name: dependency_requirement(spec) for name, spec in dependencies.items()}, - } - (run_dir / "archive-manifest.json").write_text(json.dumps(normalized, indent=2) + "\n") - (run_dir / "manifests").mkdir() - (run_dir / "manifests" / "Cargo.toml").write_text(manifest_text) - return None - - -def extract_archive(archive, extract_root, facts): - root_prefix = f"{facts['name']}-{facts['version']}" - with tarfile.open(archive, "r:gz") as archive_file: - archive_file.extractall(extract_root, filter="data") - entries = sorted(entry.name for entry in extract_root.iterdir()) - if entries != [root_prefix]: - return f"extracted entries {entries} != [{root_prefix}]" - return None - - -def check_metadata(meta, facts): - packages = {package["name"]: package for package in meta["packages"]} - for name in packages: - if name != facts["name"] and name.startswith("tuplities-"): - return f"satellite package in metadata: {name}" - by_id = {package["id"]: package for package in meta["packages"]} - members = [by_id[member_id]["name"] for member_id in meta["workspace_members"]] - if members != [facts["name"]]: - return f"workspace members {members} != [{facts['name']}]" - if set(packages) != {facts["name"], "typenum"}: - return f"metadata packages {sorted(packages)} != [{facts['name']}, 'typenum']" - package = packages[facts["name"]] - if package["version"] != facts["version"]: - return f"package version {package['version']!r} != {facts['version']!r}" - if package.get("rust_version") != facts["rust_version"]: - return f"package rust_version {package.get('rust_version')!r} != {facts['rust_version']!r}" - production = [dep for dep in package["dependencies"] if dep.get("kind") in (None, "normal")] - if [dep["name"] for dep in production] != ["typenum"]: - return f"production dependencies {sorted(dep['name'] for dep in production)} != ['typenum']" - if production[0].get("optional"): - return "typenum dependency is optional" - return None - - -def verify_metadata(text, facts, run_dir): - try: - meta = json.loads(text) - except (ValueError, IndexError) as exc: - return f"metadata output unparseable: {exc}" - error = check_metadata(meta, facts) - if error: - return error - (run_dir / "metadata.json").write_text(json.dumps(meta, indent=2) + "\n") - return None - - - -def main(): - run_dir = OUTPUT_ROOT / (datetime.now(timezone.utc).strftime("run-%Y%m%d-%H%M%S-pid") + str(os.getpid())) - run_dir.mkdir(parents=True) - temp_base = Path(tempfile.mkdtemp(prefix="tuplities-package-verify-")) - extract_root = temp_base / "source" - extract_root.mkdir() - steps = [] - facts = None - failure = None - archive_path = None - - def record(name, status, duration, log=None): - entry = {"step": name, "status": status, "duration": round(duration, 2)} - if log is not None: - entry["log"] = str(log) - steps.append(entry) - - def run_step(name, cmd, cwd, env, log, timeout, verify=None): - identity = f"{run_dir.name}/{name}" - start = time.monotonic() - error = None - try: - exit_code, stdout = run_logged(cmd, cwd, env, log, identity, timeout) - if exit_code != 0: - error = f"exit={exit_code}" - elif verify is not None: - error = verify(stdout) - except Exception as exc: - error = f"{type(exc).__name__} {exc}" - duration = time.monotonic() - start - record(name, "ok" if error is None else "failed", duration, log) - if error is not None: - raise StepFailure(f"{name}: {error}, log={log}") - - def process_step(name, func): - start = time.monotonic() - try: - result = func() - except Exception as exc: - record(name, "failed", time.monotonic() - start) - raise StepFailure(f"{name} failed ({type(exc).__name__}) {exc}") from exc - record(name, "ok", time.monotonic() - start) - return result - - try: - facts = process_step("checkout-manifest", checkout_facts) - name = facts["name"] - version = facts["version"] - - def versions(): - lines = [] - for label, command in ( - ("cargo", CARGO + ["--version"]), - ("msrv-cargo", MSRV_CARGO + ["--version"]), - ): - lines.append(f"{label}={capture(command, ROOT, os.environ, VERSION_TIMEOUT).strip()}") - rustc = ["rustc", "--version"] - if len(MSRV_CARGO) > 1 and MSRV_CARGO[1].startswith("+"): - rustc = ["rustup", "run", MSRV_CARGO[1][1:], *rustc] - lines.append(f"msrv-rustc={capture(rustc, ROOT, os.environ, VERSION_TIMEOUT).strip()}") - (run_dir / "versions.txt").write_text("\n".join(lines) + "\n") - - process_step("versions", versions) - - package_cmd = ["nice", "-n", "10", *CARGO, "package", "-p", name, "--locked", "-j", "4"] - if ALLOW_DIRTY: - package_cmd.append("--allow-dirty") - run_step("package", package_cmd, ROOT, os.environ, run_dir / "package.log", PACKAGE_TIMEOUT) - archive = ROOT / "target" / "package" / f"{name}-{version}.crate" - if not archive.is_file(): - raise StepFailure(f"expected archive missing: {archive}") - archive_path = archive - - def archive_inspect(): - error = inspect_archive(archive, run_dir, facts) - if error: - raise StepFailure(error) - - process_step("archive-inspect", archive_inspect) - - def extract(): - error = extract_archive(archive, extract_root, facts) - if error: - raise StepFailure(error) - - process_step("extract", extract) - extracted = extract_root / f"{name}-{version}" - - run_step( - "metadata", - ["nice", "-n", "10", *MSRV_CARGO, "metadata", "--format-version", "1", "--locked"], - extracted, - os.environ, - run_dir / "metadata.log", - META_TIMEOUT, - verify=lambda text: verify_metadata(text, facts, run_dir), - ) - - for case_name, extra in DOCTEST_CASES: - doc_env = {**os.environ, "CARGO_TARGET_DIR": str(run_dir / "target" / f"doc-{case_name}")} - run_step( - f"doctest-{case_name}", - ["nice", "-n", "10", *MSRV_CARGO, "test", "-p", name, "--doc", "--locked", "-j", "4", *extra], - extracted, - doc_env, - run_dir / f"doctest-{case_name}.log", - TEST_TIMEOUT, - ) - - for width in BRIDGE_WIDTHS: - features = "flatten-nest" if width == 8 else f"flatten-nest,size-{width}" - bridge_env = { - **os.environ, - "CARGO_TARGET_DIR": str(run_dir / "target" / f"bridge-{width}"), - } - run_step( - f"bridge-{width}", - [ - "nice", "-n", "10", *MSRV_CARGO, "test", "-p", name, - "--no-default-features", f"--features={features}", - "--test", "flat_bridge_contracts", - "--test", "flat_bridge_sizes", - "--locked", "-j", "4", - ], - extracted, - bridge_env, - run_dir / f"bridge-{width}.log", - TEST_TIMEOUT, - ) - - harness_env = { - **os.environ, - "CARGO": shlex.join(MSRV_CARGO), - "TUPLITIES_PACKAGE_DIR": str(extracted), - "TUPLITIES_VERIFICATION_DIR": str(run_dir / "compile-workloads"), - } - run_step( - "compile-workloads", - [str(ROOT / "tools" / "verify_compile_workloads.sh")], - ROOT, - harness_env, - run_dir / "compile-workloads.log", - WORKLOADS_TIMEOUT, - ) - except StepFailure as step_failure: - failure = str(step_failure) - finally: - shutil.rmtree(temp_base) - summary = { - "run_dir": str(run_dir), - "package": (facts or {}).get("name"), - "version": (facts or {}).get("version"), - "msrv_toolchain": MSRV_TOOLCHAIN, - "msrv_cargo": shlex.join(MSRV_CARGO), - "allow_dirty": ALLOW_DIRTY, - "archive": str(archive_path) if archive_path else None, - "steps": steps, - "failure": failure, - } - (run_dir / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") - - print(f"run dir: {run_dir}") - if archive_path is not None: - print(f"archive: {archive_path}") - if failure is not None: - print(f"FAILED {failure}", file=sys.stderr) - return 1 - print("ok") - return 0 - - -sys.exit(main()) -PY From 918b07a15eba6486acc00d49df021168ebeee3e4 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sun, 4 Oct 2026 20:59:52 +0200 Subject: [PATCH 3/3] Simplify crate CI --- .github/workflows/rust.yml | 283 ++++++++------------------------- .github/workflows/security.yml | 12 +- 2 files changed, 75 insertions(+), 220 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8f565d9..98878d1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,11 +2,9 @@ name: Rust CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] - schedule: - - cron: '0 3 * * 1' + branches: [main] workflow_dispatch: env: @@ -18,167 +16,64 @@ permissions: jobs: test: - name: Test - if: github.event_name == 'push' || github.event_name == 'pull_request' + name: Tests (${{ matrix.os }}) runs-on: ${{ matrix.os }} - timeout-minutes: 45 + timeout-minutes: 30 strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - rust: [stable, beta, nightly] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: ${{ matrix.rust }} - - name: Cache cargo registry - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo index - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo build - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: target - key: ${{ runner.os }}-cargo-build-${{ matrix.rust }}-default-${{ hashFiles('**/Cargo.lock') }} - - name: Run tests - run: cargo test -p tuplities --verbose - - name: Run tests (release) - run: cargo test -p tuplities --release --verbose + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable + - run: cargo test -p tuplities --locked + - run: cargo test -p tuplities --all-features --locked - fmt: - name: Rustfmt - if: github.event_name == 'push' || github.event_name == 'pull_request' + quality: + name: Formatting, linting, and documentation runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: stable - components: rustfmt - - name: Check formatting - run: cargo fmt -p tuplities -- --check - - clippy: - name: Clippy - if: github.event_name == 'push' || github.event_name == 'pull_request' - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: stable - components: clippy - - name: Run clippy - run: cargo clippy -p tuplities --all-targets --all-features -- -D warnings - - doc: - name: Documentation - if: github.event_name == 'push' || github.event_name == 'pull_request' - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: stable - - name: Check documentation - run: cargo doc -p tuplities --no-deps --all-features --document-private-items - env: - RUSTDOCFLAGS: -D warnings + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable + components: rustfmt, clippy + - run: cargo fmt -p tuplities -- --check + - run: cargo clippy -p tuplities --all-targets --all-features --locked -- -D warnings + - run: cargo doc -p tuplities --no-deps --all-features --locked + env: + RUSTDOCFLAGS: -D warnings msrv: - name: MSRV 1.85.0 - if: github.event_name == 'push' || github.event_name == 'pull_request' - runs-on: ubuntu-latest - timeout-minutes: 45 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: 1.85.0 - - name: Cache cargo registry - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo index - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo build - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: target - key: ${{ runner.os }}-cargo-build-msrv-1.85.0-all-and-core-${{ hashFiles('**/Cargo.lock') }} - - name: Run tests - run: cargo test -p tuplities --all-features - - name: Run tests (no default features) - run: cargo test -p tuplities --no-default-features - - embedded: - name: no_std thumbv7em-none-eabi - if: github.event_name == 'push' || github.event_name == 'pull_request' + name: MSRV and no_std runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 15 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: 1.85.0 - targets: thumbv7em-none-eabi - - name: Cache cargo registry - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo index - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo build - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: target - key: ${{ runner.os }}-cargo-build-embedded-1.85.0-thumbv7em-none-eabi-core-${{ hashFiles('**/Cargo.lock') }} - - name: Check embedded target - run: cargo check -p tuplities --target thumbv7em-none-eabi --no-default-features + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: 1.85.0 + targets: thumbv7em-none-eabi + - run: cargo check -p tuplities --lib --all-features --locked + - run: cargo check -p tuplities --lib --no-default-features --locked + - run: cargo check -p tuplities --lib --no-default-features --target thumbv7em-none-eabi --locked features: - name: Features ${{ matrix.features }} - if: github.event_name == 'push' || github.event_name == 'pull_request' + name: Features (${{ matrix.features || 'core' }}) runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 15 strategy: matrix: features: + - "" - alloc - flatten-nest - flatten-nest,size-16 @@ -189,74 +84,36 @@ jobs: - flatten-nest,size-128 - flatten-nest,size-16,size-32 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: stable - - name: Cache cargo registry - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo index - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo build - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: target - key: ${{ runner.os }}-cargo-build-features-stable-${{ strategy.job-index }}-${{ hashFiles('**/Cargo.lock') }} - - name: Run tests - env: - FEATURES: ${{ matrix.features }} - run: nice -n 10 cargo test -p tuplities --no-default-features --features "$FEATURES" + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: stable + - run: cargo test -p tuplities --no-default-features --features "$FEATURES" --locked + env: + FEATURES: ${{ matrix.features }} coverage: - name: Code Coverage - if: github.event_name == 'push' || github.event_name == 'pull_request' + name: Coverage runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 30 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: nightly - components: llvm-tools-preview - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@e407f7bafb71fd004bc5c2da3032e5470cbb6ef0 - with: - tool: cargo-llvm-cov - fallback: none - - name: Cache cargo registry - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo index - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo build - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: target - key: ${{ runner.os }}-cargo-build-coverage-nightly-all-${{ hashFiles('**/Cargo.lock') }} - - name: Generate coverage - run: | - cargo llvm-cov -p tuplities --all-targets --all-features --no-report - cargo llvm-cov -p tuplities --doc --all-features --no-clean --lcov --output-path target/coverage.lcov - - name: Upload coverage to Codecov - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: target/coverage.lcov - fail_ci_if_error: false + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 + with: + toolchain: nightly + components: llvm-tools-preview + - uses: taiki-e/install-action@e407f7bafb71fd004bc5c2da3032e5470cbb6ef0 + with: + tool: cargo-llvm-cov + fallback: none + - run: cargo llvm-cov -p tuplities --all-targets --all-features --locked --no-report + - run: cargo llvm-cov -p tuplities --doc --all-features --locked --no-clean --lcov --output-path target/coverage.lcov + - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: target/coverage.lcov + fail_ci_if_error: false diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index d003d71..d10286b 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -20,11 +20,9 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: Install Rust - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 - with: - toolchain: stable - name: Install cargo-audit - run: cargo install cargo-audit - - name: Run security audit - run: cargo audit + uses: taiki-e/install-action@e407f7bafb71fd004bc5c2da3032e5470cbb6ef0 + with: + tool: cargo-audit + fallback: none + - run: cargo audit