const-num-traits: const-trait port + modern op coverage + typestates - #7
Conversation
Squashed working state on top of the merged fork base, for PR review. Ports the num-traits surface to const traits via `c0nst` (byte-identical on stable, `const trait`/`impl const` on nightly), and modernizes it: - by-value receivers + associated `Output` (implementable by both Copy and non-Copy types) - capability splits (`PrimBits`, `Signum`, `RingOps`, `FromStrRadix`) + prelude - complete nightly-std integer/float op coverage (checked/wrapping/saturating/ overflowing/strict variants, rounding, ilog/isqrt/pow2, bits, mixed-sign, conversions, float_ops, from_ascii, format_into) - `ops/carrying.rs` (std `bigint_helper_methods`: carrying/borrowing/widening) split from `ops/clmul.rs` (carry-less GF(2) multiplication) - opt-in `typestate` feature (`PowerOfTwo`) + pure-core `personality` marker (`Ct`/`Nct`) - `ct` feature (subtle-backed masked ops); `libm`/`nightly`/`nightly-std` - MSRV 1.86 Docs: DESIGN.md, MIGRATION.md, API_BREAKS.md, COVERAGE.md, CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ntax) rust PR #158009 (2026-06-17, oli-obk) replaced `impl const Trait` with `const impl Trait`. The c0nst 0.2.1 macro still emits the old form, so any nightly >= 2026-06-19 fails the `nightly`/`nightly-std` features with "expected a trait, found type". Pin to the last good nightly (2026-06-18, rustc c1b22f44c) as a temporary stopgap. NIGHTLY_PIN.md documents the symptom, root cause, bisect, why c0nst is the unaccounted-for case, and the two unpin paths (wait for c0nst, or migrate our source `impl c0nst` -> `c0nst impl`). Known follow-up (NOT addressed): this rust-toolchain.toml overrides CI toolchains too, so the stable/MSRV jobs would run on the pinned nightly. See the "CI note" in NIGHTLY_PIN.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ghtly pin rust PR #158009 (in nightly >= 2026-06-19) replaced `impl const Trait` with `const impl Trait`. Because c0nst 0.2.1 does positional `c0nst`->`const` substitution, moving the keyword before `impl` in our source makes it emit the new `const impl` on nightly and plain `impl` on stable — the durable fix for the breakage the 2026-06-18 pin was working around. - 160 `impl c0nst Trait` sites -> `c0nst impl Trait`, including the macro metavariable forms (`impl c0nst $trait_name`/`$name`). `pub c0nst trait` declarations and `[c0nst]` bounds are unchanged (#158009 only moved the keyword on impls). - Remove rust-toolchain.toml; the source now requires nightly >= 2026-06-19. - NIGHTLY_PIN.md marked resolved; HOW_TO_FIX.md records the procedure. Verified: stable (114 lib tests) and the previously-broken nightly f428d123a (2026-06-19) both build `--features nightly` green; const_nightly canary 7/7. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f_pow2, ct ctors; Parity-for-&T Completes the resident typestate families per the final-shape synthesis — all behind the `typestate` feature (default-feature surface unchanged): - D1: blanket `impl<T: Parity + Copy> Parity for &T`, so `from_ref`-style borrowed constructors borrow-check for primitives (parity.rs). - PowerOfTwoOps gains `next_multiple_of_pow2` (branch-free align-up) and `checked_next_multiple_of_pow2`. - NonZero bridge: `HasNonZero` (assoc `type NonZero = core::num::NonZero<Self>`; no `Into<Self>` bound — non-const, would poison const consumers — value via a const `nonzero_get` accessor) + `DivNonZero` (`div_nonzero`/`rem_nonzero`, fresh `Output`, unsigned only; doc notes primitive codegen win ~0). - Sign typestates `NonNegative`/`Positive` (signed-only): infallible unsigned cast, branch-free abs, total isqrt, + const-fn refinement narrowings (`Positive -> NonNegative`, `Positive -> NonZero`). - `Odd<T>` (+ sibling `Even<T>`): bare proofs (no consuming op here; Odd's consumer is modmath). Even was cut by the synthesis; re-added bare to round out the ct-constructor set — drop if the cut should stand. - `ct` feature: 6 `CtOption`-returning masked constructors — `new_ct` on PowerOfTwo / NonNegative / Positive / Odd / Even + `CtNonZero::into_nonzero_ct` — over the existing CtIsPowerOfTwo / CtParity / CtIsZero primitives. Verified: default 114, typestate 122, typestate+ct 126, nightly+typestate 122 (const traits), typestate doctest green, zero warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
typestate is pure-core and zero-cost when unused, so — unlike `ct`, which gates the real `subtle` dependency — there's no reason to feature-gate it. Gating only kept the names out of the default surface, not worth the opt-in friction for a crate that's already a heavy num-traits fork. Make it always available. - drop `#[cfg(feature = "typestate")]` from ops/mod.rs, the root re-exports, and the const_nightly canary; the `CtNonZero` re-export now gates on `ct` only. - the prelude exports the typestate *traits* (PowerOfTwoOps / HasNonZero / DivNonZero, + ct `CtNonZero`) for method resolution but NOT the wrapper *types* — keeps generic names (Odd/Even/Positive/NonNegative) out of `prelude::*`. - remove the now-pointless vestigial `typestate = []` and `i128 = []` no-op features (no legacy users to preserve; nothing gates on either). Verified: default 122 lib tests, ct 126, nightly green, zero warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the four families previously deferred from the typestate layer. - `BitIndex<T>` (all 12 ints, u32 rep like `PowerOfTwo`) + `BitIndexOps` (c0nst): `shl_index`/`shr_index` by an amount proven `< BITS`, so the shift carries no overflow-check branch and needs no `unbounded_*` masking. No `new_ct` — shift amounts are public parameters per the crate CT convention. - `NonMin<T>` (signed, repr(transparent)): proof that a value is `!= MIN`. Spends it on total `neg`/`abs` (the sole overflow is at `MIN`) and, as the dividend, total signed `div_nonzero`/`rem_nonzero` by a `core::num::NonZero` divisor — the only signed-division overflow is `MIN / -1`, excluded by construction. This is the co-proof unsigned `DivNonZero` can do without. `Positive`/`NonNegative` narrow here for free via `into_nonmin`; adds a `new_ct` masked constructor. - `Finite<T>` (f32/f64, repr(transparent)): const `new` via an exponent-bit test (sidesteps the non-const `is_finite`). Spent by a total `Ord`/`Eq` — with NaN excluded `partial_cmp().unwrap()` can't panic — so finite floats become sortable / `BTreeMap`-keyable, which bare `f32`/`f64` aren't. No `new_ct`: floats are outside the constant-time model. Updates the `DivNonZero` doc (the NonMin co-proof is no longer "deferred"), documents the whole typestate module in CLAUDE.md, and adds unit + doctest + nightly-const-canary coverage. Verified: stable 126 lib / 20 integration / 257 doctests, ct 130, nightly 126 + 9 const-canary, no_std builds, zero warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`Odd::new` / `Even::new` gain a `where T: [const] Parity` refinement, so on nightly they are `const`-callable for any backend whose `Parity` impl is const (plain on stable, signature unchanged). This is the panic-free-north-star path from modmath's roundup: a compile-time-constant modulus can be proven odd in a `const` block, so a downstream `Field::new(p).unwrap()` becomes a compile error instead of a surviving `panic_fmt` symbol. The `[const]` bound rides on the fn's where-clause (an inherent impl header can't carry it); `Copy` covers the drop, so no `Destruct` bound is needed. Also corrects a stale CLAUDE.md line: the `Overflowing*` family IS root-exported (has been since the squash), not module-path-only — that re-export is what resolves modmath's `use const_num_traits::OverflowingAdd` on nightly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Untracks the design/process notes that shouldn't live in the fork's history (kept on disk locally): NIGHTLY_PIN.md, HOW_TO_FIX.md, CLAUDE.md, DESIGN.md, COVERAGE.md, API_BREAKS.md. MIGRATION.md (downstream-facing) and README.md stay. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @kaidokert, your pull request is larger than the review limit of 300000 diff characters
|
Warning Review limit reached
More reviews will be available in 23 minutes and 4 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (19)
📝 WalkthroughWalkthroughThis PR transforms Changesconst-num-traits API expansion and restructure
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request transitions the num-traits fork to const-num-traits, introducing a comprehensive suite of const-compatible numeric traits, carry-less and carry-propagating arithmetic, and zero-cost typestate proofs. Key changes include migrating calling conventions to pass arguments by value, splitting aggregate traits like PrimInt and Signed into capability-pure components, and adding constant-time operations. The review feedback identifies several timing side-channel vulnerabilities in the carry-less multiplication, bit-manipulation, and constant-time typestate constructor implementations, where branching on secret-derived data violates the documented constant-time (Tier A) guarantees. Actionable branchless alternatives are provided to eliminate these timing leaks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3a3bc7e004
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… tripwire The proofs' only *safe* constructor was the per-primitive macro `new`; a non-primitive carrier (fixed big-integer, CT word) could build them only via the `unsafe` `from_*_unchecked`. That forces `unsafe` downstream and blocks e.g. `PowerOfTwoOps for FixedUInt` — a typestate that can't be constructed safely for the crate's own carrier types is upstream-broken. - `PowerOfTwo::new_checked` (`T: IsPowerOfTwo + PrimBits`): recovers the exponent as `BITS-1-leading_zeros` (width = `T::ZERO.count_zeros()`); the `unsafe` stays inside the crate. Per-primitive `const new` stays the fast path. - `BitIndex::new_checked` (`T: PrimBits`): `index < T::BITS` via the same width. - Both are `[c0nst]`-bounded, so const-callable on nightly for const carriers. Tripwire: tests/typestate_generic_carrier.rs constructs every carrier-generic proof (PowerOfTwo/BitIndex/Odd) from a non-primitive `Carrier` with no `unsafe`, via capability-bound-only helpers. If a future change re-narrows construction to primitives, it fails to compile — the regression guard mirroring prim_bits.rs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/ops/wrapping.rs (1)
170-185:⚠️ Potential issue | 🟠 MajorChange shift trait bounds from
usizetou32to match parameter types and return projections.The
WrappingShlandWrappingShrtraits acceptrhs: u32but declare trait bounds and return type projections usingShl<usize>andShr<usize>. For custom types whereShl<u32>::Outputdiffers fromShl<usize>::Output, this mismatch advertises an incorrect return contract.Update trait bounds, return projections, and
Wrapping<T>where clauses fromusizetou32:Changes required
-pub c0nst trait WrappingShl: Sized + [c0nst] Shl<usize> { +pub c0nst trait WrappingShl: Sized + [c0nst] Shl<u32> { - fn wrapping_shl(self, rhs: u32) -> <Self as Shl<usize>>::Output; + fn wrapping_shl(self, rhs: u32) -> <Self as Shl<u32>>::Output; -pub c0nst trait WrappingShr: Sized + [c0nst] Shr<usize> { +pub c0nst trait WrappingShr: Sized + [c0nst] Shr<u32> { - fn wrapping_shr(self, rhs: u32) -> <Self as Shr<usize>>::Output; + fn wrapping_shr(self, rhs: u32) -> <Self as Shr<u32>>::Output; - Wrapping<T>: Shl<usize, Output = Wrapping<T>>, + Wrapping<T>: Shl<u32, Output = Wrapping<T>>, - Wrapping<T>: Shr<usize, Output = Wrapping<T>>, + Wrapping<T>: Shr<u32, Output = Wrapping<T>>,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/ops/wrapping.rs` around lines 170 - 185, The WrappingShl trait definition declares trait bounds using Shl<usize> but the wrapping_shl method accepts rhs: u32, creating a type mismatch. Change the trait bounds in the WrappingShl trait declaration from Shl<usize> to Shl<u32>, and update the return type projection in the wrapping_shl method signature from <Self as Shl<usize>>::Output to <Self as Shl<u32>>::Output. Apply the same changes to the WrappingShr trait and any where clauses in Wrapping<T> implementations that reference usize shift operators, changing them all to u32 to match the parameter types.
🧹 Nitpick comments (1)
src/ops/overflowing.rs (1)
8-9: 🧹 Nitpick | 🔵 TrivialRemove legacy numeric constant imports.
The primitive types (
i8,i16,i32,i64,i128,isize,u8,u16,u32,u64,u128,usize) and their associated constants are available directly without importing thecore::*modules. The code only uses primitive associated constants likei8::MINandi16::MAX, which resolve without these imports.♻️ Suggested cleanup
-use core::{i128, i16, i32, i64, i8, isize}; -use core::{u128, u16, u32, u64, u8, usize};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/ops/overflowing.rs` around lines 8 - 9, Remove the two import statements that import numeric primitive types from core module (the lines importing i8, i16, i32, i64, i128, isize and u8, u16, u32, u64, u128, usize). These imports are redundant because primitive type constants like i8::MIN and i16::MAX are available directly in Rust without explicit imports from the core module. Delete both legacy import lines entirely.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@MIGRATION.md`:
- Around line 7-8: The MIGRATION.md file contains references to documentation
files that do not exist in the repository: API_BREAKS.md, COVERAGE.md,
DESIGN.md, and CLAUDE.md are referenced on lines 8, 27, and 75-76 respectively.
Either create these missing documentation files in the repository root with
appropriate content for each, or update all the references in MIGRATION.md to
point to actual documentation files that exist in the repository. Ensure all
links are relative paths and that the documentation content aligns with what the
MIGRATION.md guide is trying to reference.
In `@README.md`:
- Around line 1-32: The README has rebranded the crate as const-num-traits but
the dependency installation snippets still reference the old num-traits crate
name, which will misdirect users. Update both dependency configuration examples
in the file: replace num-traits with const-num-traits in the first inline
dependency snippet, and in the second detailed example update the
[dependencies.num-traits] section header to [dependencies.const-num-traits]
while also changing the version from "0.2" to "0.1" in both places to match the
actual package version.
In `@src/cast.rs`:
- Around line 91-103: The rustdoc comments for both the to_f32 and to_f64
functions contain a misspelling of the word "infinity" as "inifinity". Fix this
typo by correcting "inifinity" to "infinity" in both function documentation
comments.
In `@src/int.rs`:
- Around line 20-30: The PrimBits trait currently inherits from ConstZero and
ConstOne, which transitively require Add and Mul traits respectively,
undermining the design intent of PrimBits as a capability-pure bit layer without
full arithmetic trait contracts. Instead of inheriting ConstZero and ConstOne in
the PrimBits trait definition, create a minimal ConstBitIdentities trait that
provides only the ZERO and ONE constants needed for bit-twiddling operations
without exposing arithmetic trait requirements, or alternatively inline these
constants directly in PrimBits. Apply the same separation principle to the
PrimInt trait (which inherits from PrimBits) to ensure consistency across the
trait hierarchy.
In `@src/ops/bits.rs`:
- Around line 12-16: The documentation comment classifies DepositBits and
ExtractBits as Tier A constant-time operations, but the fallback implementations
at lines 464-475 and 485-496 contain mask-dependent loop counts and
data-dependent branches in the deposit_bits and extract_bits functions. Either
update the documentation comment to clarify that these fallback implementations
require both operands to be public to maintain constant-time guarantees, or
refactor the deposit_bits and extract_bits implementations to use fixed-width
masked logic without data-dependent branches to achieve true Tier A
constant-time behavior for secret operands.
In `@src/ops/checked.rs`:
- Around line 3-5: The module-level documentation claim that operations are
branchless overstates the constant-time guarantee because the file includes
checked_div, checked_rem, and checked_pow which have data-dependent (for
division and remainder) or exponent-dependent (for exponentiation) behavior.
Update the module-level comment at the top of the file to narrow the
constant-time claim by clarifying that only certain operations are branchless,
and explicitly exclude or note that division, remainder, and exponentiation
operations are not constant-time for secret-derived inputs. Also review and
update the individual documentation comments for the checked_div, checked_rem,
and checked_pow functions (around lines 94-138 and 320-338) to make clear these
operations are not branchless.
In `@src/ops/clmul.rs`:
- Line 10: The CT tier documentation on line 10 claims branchless
implementation, but the shift/XOR fallback loops at lines 43-49, 88-95, 143-150,
and 170-177 branch on operand bits during polynomial multiplication, leaking
timing information about secret operands. Replace each fallback's bit-branching
multiplication loop with a mask-select XOR accumulation pattern where instead of
conditionally branching on each bit, use bitwise operations to create masks
(typically by sign-extending or arithmetic-shifting the bit value) and
unconditionally XOR the partial products selected by these masks, ensuring all
operand-dependent iterations execute without branches.
In `@src/ops/euclid.rs`:
- Around line 397-405: The floating-point Euclid assertions are not using
absolute values to check reconstruction errors, allowing large negative errors
to pass. For each of the four assert statements checking div_euclid and
rem_euclid reconstruction, wrap the arithmetic expression on the left-hand side
of the <= comparison (the expressions computing Euclid::div_euclid(...) * ... +
Euclid::rem_euclid(...) combined with x) in `.abs()` to verify closeness in both
positive and negative directions against the epsilon tolerance.
In `@src/ops/overflowing.rs`:
- Around line 3-5: The file-level CT tier A comment at the beginning of the
module makes a blanket statement about all operations being CT-implementable,
but this is inaccurate since OverflowingDiv, OverflowingRem, and OverflowingPow
are data-dependent or exponent-dependent operations that cannot be implemented
in constant-time. Modify the file-level comment to scope the CT tier A
designation to only the operations that are actually CT-implementable,
explicitly noting that the blanket statement does not apply to division,
remainder, or power operations. Ensure the comment at lines 3-5 clearly reflects
this limited scope.
In `@src/ops/saturating.rs`:
- Around line 1-4: The module-level documentation at the top of saturating.rs
currently classifies all saturating operations as CT tier A with the branchless
select explanation, but this does not apply to the SaturatingDiv trait since
division is value-dependent and the trait panics on zero. Split the tier wording
in the module documentation to clearly separate the CT tier A classification
from the SaturatingDiv operation, ensuring callers understand that div-based
saturation is not constant-time-safe while other saturating operations remain CT
tier A compliant.
In `@src/ops/typestate.rs`:
- Around line 543-545: The safety documentation comment for the
NonMin::new_unchecked method contains an incorrect precondition. Change the
safety contract text from stating that value must not be less than MIN to
correctly stating that value must not be equal to MIN (value != MIN), since this
is the actual invariant required by the neg, abs, and div_nonzero operations
that depend on this type's constraint.
- Around line 807-817: In the PowerOfTwo::new_ct method, the match expression
using value.checked_ilog2() introduces data-dependent control-flow branching on
the condition value == 0, which leaks secret information in a constant-time
constructor. Replace the entire match statement that assigns the exp variable
with a single branchless call to value.trailing_zeros() (which returns the bit
position directly without branching), since the result is masked out by the
choice CtChoice anyway and does not require special handling for zero values.
In `@src/ops/wrapping.rs`:
- Around line 3-4: The module-level documentation comment claims all builtin
wrapping operations are branchless and universally constant-time safe, but this
is incorrect because WrappingDiv, WrappingRem, and WrappingPow have
data-dependent branching patterns. Update the documentation comment at the top
of the module to clarify that only certain wrapping operations are universally
CT-safe, and explicitly note that WrappingDiv, WrappingRem, and WrappingPow are
NOT universally constant-time safe due to their data-dependent behavior. Also
update the individual documentation comments for WrappingDiv, WrappingRem, and
WrappingPow to clearly indicate they should not be treated as safe for secret
data.
In `@src/sign.rs`:
- Around line 77-79: The abs method is using direct negation (-self) which
overflows when called on signed integer MIN values, causing panics in debug mode
and compilation errors in const contexts. Replace the negation operation with
wrapping_neg() in the abs method implementation (within the c0nst::c0nst! macro
block) to handle overflow correctly and return MIN as expected per the
documentation. Verify that wrapping_neg() is available as a stable const fn
under MSRV 1.86 for all target signed integer types; if not available in that
version, an alternative approach using conditional logic or bit manipulation may
be needed.
---
Outside diff comments:
In `@src/ops/wrapping.rs`:
- Around line 170-185: The WrappingShl trait definition declares trait bounds
using Shl<usize> but the wrapping_shl method accepts rhs: u32, creating a type
mismatch. Change the trait bounds in the WrappingShl trait declaration from
Shl<usize> to Shl<u32>, and update the return type projection in the
wrapping_shl method signature from <Self as Shl<usize>>::Output to <Self as
Shl<u32>>::Output. Apply the same changes to the WrappingShr trait and any where
clauses in Wrapping<T> implementations that reference usize shift operators,
changing them all to u32 to match the parameter types.
---
Nitpick comments:
In `@src/ops/overflowing.rs`:
- Around line 8-9: Remove the two import statements that import numeric
primitive types from core module (the lines importing i8, i16, i32, i64, i128,
isize and u8, u16, u32, u64, u128, usize). These imports are redundant because
primitive type constants like i8::MIN and i16::MAX are available directly in
Rust without explicit imports from the core module. Delete both legacy import
lines entirely.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 27b64830-0bf1-4e11-a8f6-9254a358e880
📒 Files selected for processing (38)
Cargo.tomlMIGRATION.mdREADME.mdsrc/bounds.rssrc/cast.rssrc/identities.rssrc/int.rssrc/lib.rssrc/ops/bits.rssrc/ops/bytes.rssrc/ops/carrying.rssrc/ops/checked.rssrc/ops/clmul.rssrc/ops/convert.rssrc/ops/ct.rssrc/ops/euclid.rssrc/ops/float_ops.rssrc/ops/format_into.rssrc/ops/from_ascii.rssrc/ops/inv.rssrc/ops/log.rssrc/ops/mixed.rssrc/ops/mod.rssrc/ops/mul_add.rssrc/ops/overflowing.rssrc/ops/parity.rssrc/ops/pow2.rssrc/ops/rounding.rssrc/ops/saturating.rssrc/ops/sqrt.rssrc/ops/strict.rssrc/ops/typestate.rssrc/ops/wrapping.rssrc/personality.rssrc/pow.rssrc/sign.rstests/const_nightly.rstests/prim_bits.rs
MSRV 1.86 already clears edition 2024's 1.85 floor; no source migration was needed (stable/no_std/ct/nightly + all doctests build unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-doc refs Two threads, intermingled across lib.rs/typestate.rs: API additions (typestate.rs, lib.rs): - `impl AsRef<T>` on Odd/Even — borrow the proven value without consuming the proof (the out-direction; the in-direction stays the inherent `from_ref`, which has no core trait to mirror since core has no fallible-ref conversion). - `impl TryFrom` checked constructors on every value-carrying proof (Odd/Even/NonNegative/Positive/NonMin/PowerOfTwo/BitIndex/Finite) returning a shared `TypestateError` (Display + core::error::Error, no_std-clean). This is the boundary adapter `Option`-returning `new` can't be: `?`-propagation into Result fns, generic `TryInto` interop, inference-driven `.try_into()` call sites. `new`/`new_checked` remain the const + generic-carrier path; TryFrom is per-primitive (a generic `TryFrom<T> for Proof<T>` collides with core's reflexive blanket). examples/typestates.rs: runnable feature-usage example across all proofs (gating the ct section), doubling as a compile-checked guarantee. Docs cleanup: drop references to internal-only design docs (DESIGN.md/CLAUDE.md) from source comments and trim the over-long prose added in the typestate pass — each comment is now self-contained. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adapted from fixed-bigint-rs (the most comprehensive text-cleaning set across the sibling crates — the only one with fix-byte-order-marker): the 9 standard pre-commit-hooks cleaners plus doublify fmt/cargo-check/clippy. Google copyright header dropped. Existing tree already passes every text hook clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Running pre-commit surfaced two classes of issue, both downstream of the
edition-2024 bump:
- rustfmt under style-edition 2024 reformats the whole crate (e.g. short
if/else onto one line), including the verbatim-upstream float files. Applied;
semantically identical.
- edition-2024 clippy is stricter. Modernized in-code (the chosen policy over
blanket allows):
- legacy_numeric_constants: drop `use core::{u8,..}` legacy module imports
(bounds/cast/int/overflowing); usages resolve to the associated consts.
`::core::f32::NAN` -> `f32::NAN` in the float parser/tests.
- legacy numeric methods: `$t::min_value()/max_value()` -> `$t::MIN/MAX`.
- approx_constant / excessive_precision on the FloatConst table: delegate
TAU/LOG10_2/LOG2_10 to `$T::consts::*` instead of literals (exact, and the
same literal previously fed both f32 and f64 — truncating it broke a test).
Test PI literals -> `core::f{32,64}::consts::PI`.
- descriptive names: `_2`/`_10` -> `two`/`ten`.
Two lints are genuine false-positives for this crate's design and get a
documented crate-level allow rather than a rewrite:
- wrong_self_convention: `is_*` predicates take `self` by value, mirroring
core's inherent methods (the crate-wide by-value convention).
- manual_range_contains: the const parsers can't call the non-const
`RangeInclusive::contains`, so range checks stay longhand.
Line-comment review from gemini / codex / coderabbit on PR #7. CT (security-tagged, gemini + codex): make the Tier-A bodies branchless on the operand instead of branching on its bits — - clmul: carryless_mul / widening_carryless_mul / carrying_carryless_mul and the wide_clmul_u64 helper now mask the partial product (`((rhs>>i)&1).wrapping_neg()`). - bits: deposit_bits / extract_bits mask the conditional `|=` on the operand bit; the loop count still tracks popcount(mask), which is the public mask parameter. - typestate: PowerOfTwo::new_ct computes the exponent as `trailing_zeros() % BITS` (branchless, safe for 0) instead of the zero-branching checked_ilog2. CT-tier docs (coderabbit): the module headers claimed Tier A/B branchless but the families now include div/rem/pow, which are value/exponent-dependent. Scoped the wording in checked/overflowing/saturating/wrapping to exclude div/rem/pow (Tier C), and documented that deposit/extract are Tier A only for a public mask. Doc/correctness (coderabbit): fix `inifinity` typo in two ToPrimitive docs; take the absolute reconstruction error in the float Euclid asserts (a large negative residual previously passed `<= eps`); point the README dependency snippets at `const-num-traits` instead of the inherited `num-traits`.
PrimBits is documented as a capability-pure bit layer, but it transitively required Add + Mul: PrimBits: ConstZero: Zero: Add and ConstOne: One: Mul. Those operators were never used — every PrimBits body uses only bit ops and the ZERO/ONE *constants* — so the requirement was gratuitous, and it blocked a true bit-only constant-time integer from implementing PrimBits (it would have had to supply Add/Mul it doesn't want). Make ConstZero/ConstOne pure constant-carriers (`: Sized`, not `: Zero`/`: One`). They now provide the compile-time ZERO/ONE without pulling in the additive/ multiplicative identity laws or their operators. Zero/One keep their Add/Mul supertraits — that coupling is correct for numeric code; only the const-carriers are decoupled. Numeric types still implement both, so nothing downstream of Num changes (PrimInt gets Zero/One/Add/Mul via Num as before). Blast radius is contained: PrimBits is the only consumer of ConstZero/ConstOne as a bound, and nothing calls zero()/one()/is_zero() through a ConstZero/ConstOne bound. tests/prim_bits.rs now proves the cut — CtWord implements PrimBits with ONLY bit ops + the two constants, no Add/Mul/Zero/One. Addresses the int.rs PrimBits review finding.
`Zero: Add` and `One: Mul` were inherited upstream convention, not a law: `0` is equally the identity under subtraction (`a - 0 = a`) and `1` under division (`a / 1 = a`), so privileging exactly `Add`/`Mul` is arbitrary. Nothing in `Zero`/`One`'s own methods (`zero`/`is_zero`/`one`/`is_one`) uses the operator either — it existed only for transitive propagation to consumers. Make them pure identity-value traits (`: Sized`). Consumers now state the operators they actually need explicitly — `T: Zero + Add`, `T: Zero + Sub`, `T: One + Div`, etc. — rather than receiving `Add`/`Mul` whether or not they use them. Code wanting the full operator set takes `Num` (operators come via `NumOps`/`RingOps`, unchanged). Blast radius in-crate: zero. The build compiles untouched — every in-crate consumer either bounds on `Num` or already names its operators — which confirms the supertrait was carrying no weight here. This is an explicit API break for downstream `T: Zero`/`T: One` call sites that relied on the implicit `+`/`*`; they add the bound they need.
…MIN)
The old `if self.is_negative() { -self } else { self }` produced three different
behaviors for the same call: abs(MIN) was a compile error in const eval, a panic
in debug, and a wrap to MIN in release. For a `const` trait that const/runtime
divergence is the worst outcome.
Make abs total so it behaves identically everywhere. Among the total options,
saturate rather than wrap: an absolute value must be non-negative, so abs(MIN)
returns MAX (the nearest representable magnitude), not MIN. `self.saturating_abs()`
gives const == debug == release and keeps `abs(x) >= 0`. abs_sub gets the same
treatment (`saturating_sub`) for the `MAX - MIN` overflow case.
Callers who want a different overflow policy use the explicit traits
(WrappingAbs/CheckedAbs/StrictAbs/OverflowingAbs) or NonMin for a
statically-proven total abs — so the plain Signed::abs is free to be the sane
default. Doc updated to match; tests added (stable: abs(MIN)==MAX, abs_sub
saturation; nightly: const ABS_MIN guard that the old impl couldn't compile).
Addresses the sign.rs abs(MIN) review finding.
The fmt jobs pinned dtolnay/rust-toolchain@1.62.0 (inherited from upstream).
Edition 2024 needs cargo >= 1.85, so 1.62's cargo failed to parse the manifest
("this version of Cargo is older than the 2024 edition") and the Format check
failed on every run. Use stable, matching the toolchain the tree is formatted
with locally. Test jobs were unaffected (they use the matrix toolchains).
State the precondition explicitly as value != MIN.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request significantly expands the const-num-traits crate by introducing a wide array of fine-grained operation traits (such as carry-less multiplication, carrying/widening arithmetic, and integer logarithms), zero-cost typestate proofs (including PowerOfTwo, BitIndex, and Finite), constant-time markers, and ASCII parsing utilities. It also restores the 'minimal impl' macros for ToPrimitive and FromPrimitive to compensate for the removal of default methods in the const-trait port. The review feedback correctly identifies a limitation in the default implementations and minimal macros for to_i128 and from_i128 in src/cast.rs, which unnecessarily restrict positive values to i64::MAX instead of supporting the full 64-bit unsigned range by falling back to u64 conversions.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
The `to_i128`/`from_i128` default impls (and the minimal-impl macros) routed
only through `to_i64`/`from_i64`, so a type holding a value in
`(i64::MAX, u64::MAX]` got `None` even though `i128` represents it exactly.
to_i128 now falls back to `to_u64` when `to_i64` fails (`u64 as i128` is always
exact); from_i128 routes non-negative values through `from_u128` (which narrows
to `from_u64`). Applied in both the trait defaults and
`impl_{to,from}_primitive_minimal!`. The trait defaults stay match/if-based
(const-callable); the macros stay plain. `u128` conversions already covered the
full unsigned range, so only the i128 side changed.
Test: a u64-backed minimal type (manual-default and macro-generated) round-trips
`1 << 63` through to_i128/from_i128 — both were `None` before.
Addresses the cast.rs i128-range review findings.
Review pass over the diff for comment quality: - Fix a real doc bug: the `Signum` trait's "returns the sign of the number" paragraph was attached to `type Output` and the terse "sign result type" to the `signum` method — swapped back. - Remove journey/litigation comments that narrated history rather than the landed code: the `Even` "synthesis cut … drop it if" aside, the `DivNonZero` "Honest note:" wrapper, the `impl_to_primitive_minimal!` "had to strip … which broke … restores" story, "Both were None before the fix", euclid's "now that FloatCore is gone", const_nightly's "the old -self impl was a const-eval error", convert's redundant "still unstable in std", carrying's "needs no c0nst! treatment" macro-meta, and trim the FromBytes/ToBytes contrast tail. - Drop verbose "same (self, u32) shape as the … shifts" macro-reuse narration in checked/wrapping/strict (the checked one also had a stale `&self`), and the redundant "float comparison is const-stable" parenthetical. - Minor: `(overflow as u8) ^ 1` -> `!overflow as u8` in ct.rs; grammar fix in the typestate carrier test. No behavior change.
Forks
num-traitsinto a const-trait crate via thec0nstmacro: byte-identical to upstream on stable, realconst trait/impl conston nightly with--features nightly.What's here
Zero/One/Num/PrimInt/Checked*/Wrapping*/Saturating*/Overflowing*/Euclid/Signed/ToPrimitive/…).strict, bigint (carrying+clmul),rounding,log/sqrt/pow2,bits,mixed-sign,convert(casts/widen/truncate),float_ops,from_ascii,format_into.Copyreuse:PrimBits,Signum,RingOps,FromStrRadix; apreludemodule.core, always available, zero-cost unused):PowerOfTwo,BitIndex, theNonZerobridge +DivNonZero,NonNegative/Positive/NonMin,Odd/Even,Finite.ctfeature (optionalsubtledep): masked-return constant-time atoms.Breaking vs
num-traits(deliberate — seeMIGRATION.md)x.checked_add(y), notx.checked_add(&y).Output; cross-type ops useSigned/Unsigned/Wide.PrimInt→PrimBits+pow,Signed→Signum, …).This is not a drop-in for
num-traits; downstream call sites need the mechanical changes inMIGRATION.md.Toolchain / features
nightly/nightly-stdare experimental — they ride bleeding-edge const-trait syntax throughc0nstand can break on rustc-nightly churn (e.g. Rejectimpl const Traitsince the right syntax isconst impl Traitnow rust-lang/rust#158009).default = ["std"],libm,std,nightly,nightly-std,ct.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
preludeandpersonalityhelpers.FromAsciiparsing and nightly-onlyFormatInto.API Changes
ToBytesto takeselfby value.Documentation
Tests