Skip to content

const-num-traits: const-trait port + modern op coverage + typestates - #7

Merged
kaidokert merged 22 commits into
const_traitsfrom
squashy
Jun 24, 2026
Merged

kaidokert merged 22 commits into
const_traitsfrom
squashy

Conversation

@kaidokert

@kaidokert kaidokert commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Forks num-traits into a const-trait crate via the c0nst macro: byte-identical to upstream on stable, real const trait / impl const on nightly with --features nightly.

What's here

  • Const-trait port of the upstream surface (Zero/One/Num/PrimInt/Checked*/Wrapping*/Saturating*/Overflowing*/Euclid/Signed/ToPrimitive/…).
  • Complete nightly-std op coverage (~45 fork-added trait families): strict, bigint (carrying + clmul), rounding, log/sqrt/pow2, bits, mixed-sign, convert (casts/widen/truncate), float_ops, from_ascii, format_into.
  • Layered atoms split out for CT/non-Copy reuse: PrimBits, Signum, RingOps, FromStrRadix; a prelude module.
  • Typestate proofs (pure-core, always available, zero-cost unused): PowerOfTwo, BitIndex, the NonZero bridge + DivNonZero, NonNegative/Positive/NonMin, Odd/Even, Finite.
  • ct feature (optional subtle dep): masked-return constant-time atoms.

Breaking vs num-traits (deliberate — see MIGRATION.md)

  • Receivers/args are by value, mirroring core's inherent methods: x.checked_add(y), not x.checked_add(&y).
  • Owned results carry an associated Output; cross-type ops use Signed/Unsigned/Wide.
  • A few traits are split (PrimIntPrimBits + pow, SignedSignum, …).

This is not a drop-in for num-traits; downstream call sites need the mechanical changes in MIGRATION.md.

Toolchain / features

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added many new const-capable operation traits for bits/bytes, carry and carry-less multiplication, integer conversions, floating-point helpers, logs, rounding, integer square roots, power-of-two utilities, strict panicking arithmetic, and mixed signedness math.
    • Introduced const-time (ct) masked operations and typestate proof types, plus new prelude and personality helpers.
    • Added FromAscii parsing and nightly-only FormatInto.
  • API Changes

    • Updated numerous traits to use value-based inputs and operator-associated output types; added “minimal impl” helpers for primitive conversions.
    • Changed ToBytes to take self by value.
    • Expanded Euclid with wrapping/overflowing variants.
  • Documentation

    • Updated README for the const-traits fork, features, and MSRV.
  • Tests

    • Added broader const-evaluation and typestate coverage, including nightly-only checks.

kaidokert and others added 8 commits June 19, 2026 22:33
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>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @kaidokert, your pull request is larger than the review limit of 300000 diff characters

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kaidokert, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 42ac1a8c-1ef3-4685-94b2-cdcb03813b3d

📥 Commits

Reviewing files that changed from the base of the PR and between 4405b73 and eff9bde.

📒 Files selected for processing (19)
  • .github/workflows/ci.yaml
  • .github/workflows/pr.yaml
  • src/cast.rs
  • src/identities.rs
  • src/int.rs
  • src/ops/bytes.rs
  • src/ops/carrying.rs
  • src/ops/checked.rs
  • src/ops/convert.rs
  • src/ops/ct.rs
  • src/ops/euclid.rs
  • src/ops/float_ops.rs
  • src/ops/strict.rs
  • src/ops/typestate.rs
  • src/ops/wrapping.rs
  • src/sign.rs
  • tests/const_nightly.rs
  • tests/prim_bits.rs
  • tests/typestate_generic_carrier.rs
📝 Walkthrough

Walkthrough

This PR transforms const-num-traits (a num-traits fork targeting Rust 2024 with MSRV 1.86+) by migrating all arithmetic trait methods from &self/&Self reference receivers to by-value semantics with associated Output types derived from operator traits. It extracts PrimBits as a capability-focused trait from PrimInt, adds 15+ new operation modules spanning carrying/carry-less multiplication, mixed-signedness arithmetic, strict/saturating/rounding/logarithmic/square-root operations, bit manipulation, integer conversion, floating-point operations, and ASCII parsing. The PR also introduces a zero-runtime-cost typestate proof system with safe constructors, an optional constant-time layer via subtle, personality markers for const-fn dispatch, a prelude module, and comprehensive integration tests demonstrating const-callability across all new trait families.

Changes

const-num-traits API expansion and restructure

Layer / File(s) Summary
Crate config, README, and pre-commit setup
Cargo.toml, README.md, .pre-commit-config.yaml
Updates edition to Rust 2024, adds optional subtle dependency with ct and nightly-std feature configuration, removes i128 feature, rebrands README for const-num-traits with MSRV 1.86+ and detailed const-trait goals, and introduces pre-commit hooks for Rust formatting and clippy checks.
c0nst impl syntax normalization
src/bounds.rs, src/identities.rs, src/ops/inv.rs, src/ops/mul_add.rs, src/ops/bytes.rs, src/cast.rs, src/lib.rs, src/sign.rs
Mechanical reordering of the c0nst keyword from impl c0nst Trait for T to c0nst impl Trait for T across all trait implementation blocks; no logic or behavior changes.
By-value CheckedAdd/Sub/Mul/Div/Rem/Neg/Shl/Shr and new variants
src/ops/checked.rs
Migrates all checked arithmetic traits from &self/&Self returning Self to value-based receivers returning <Self as Op<Self>>::Output; adds new CheckedAbs and CheckedPow traits; updates macro-generated impls and tests to match new signatures.
By-value WrappingAdd/Sub/Mul and OverflowingAdd/Sub/Mul plus extended variants
src/ops/wrapping.rs, src/ops/overflowing.rs
Refactors wrapping and overflowing traits to use by-value receivers with associated Output types; adds extended variants (WrappingDiv/Rem/Abs/Pow, OverflowingDiv/Rem/Neg/Abs/Shl/Shr/Pow); updates Wrapping blanket impls and test constraints.
By-value SaturatingAdd/Sub/Mul and new saturating traits
src/ops/saturating.rs
Migrates SaturatingAdd/Sub/Mul to value-based calls with Output types; adds new SaturatingDiv/Neg/Abs/Pow traits with documentation and implementations for integer primitives.
By-value Euclid division with wrapping/overflowing variants
src/ops/euclid.rs
Updates Euclid/CheckedEuclid to value-based signatures with associated Div/Rem output types; adds WrappingEuclid and OverflowingEuclid extending with wrapping/overflowing variants; updates float impls to use FloatCore with value-based calls; reorganizes and updates all tests.
By-value ToBytes and FromBytes
src/ops/bytes.rs
Changes ToBytes::to_*_bytes from &self to self receivers; adds Sized bound; updates macro-generated float and integer impls and test helpers to pass values directly rather than references.
By-value Signed trait and new Signum trait
src/sign.rs
Introduces new public Signum const trait with value-based signum(self) -> Output; updates Signed to extend Signum with value-based abs/abs_sub returning <Self as Neg>::Output; adds separate const implementations for Wrapping and floats; updates helper functions with new bounds.
ToPrimitive/FromPrimitive defaults and minimal impl macros
src/cast.rs
Adds comprehensive default method bodies to ToPrimitive (deriving all to_* from to_i64/to_u64 via CheckedCast) and FromPrimitive (deriving all from_* with 128-bit explicit bounds); exports impl_to_primitive_minimal! and impl_from_primitive_minimal! macros with validation test module.
lib.rs: RingOps, NumOps restructure, prelude, and personality
src/lib.rs, src/personality.rs
Adds RingOps const trait aggregating Add/Sub/Mul; updates NumOps to layer Div/Rem on top; introduces pub mod prelude with glob trait imports and feature gates; adds pub mod personality with sealed Nct/Ct marker types and PersonalityTag enum for stable const-fn dispatch; expands crate-root pub use re-exports; updates float parsing special-value handling and test UFCS call sites.
PrimBits/PrimInt trait split
src/int.rs
Extracts PrimBits as new public const trait covering bitwise/rotation/shift/byte-reversal with ConstZero/ConstOne and operator trait requirements; updates PrimInt to inherit PrimBits and focus on Num/Bounded/checked arithmetic; restructures prim_int_impl! to generate separate impl blocks for each trait; updates internal helpers and tests.
Carrying and carry-less multiplication ops
src/ops/carrying.rs, src/ops/clmul.rs
Adds CarryingAdd/BorrowingSub with unsigned and signed semantics; CarryingMul with widening, including hand-rolled wide_mul_u128 for 128-bit with sign-correction; WideningMul for std pairs. Separately adds CarrylessMul, WideningCarrylessMul, CarryingCarrylessMul with Karatsuba-style u128 composition; all include comprehensive tests validating carry/borrow chaining and 128-bit correctness.
Mixed-signedness arithmetic, strict ops, and rounding
src/ops/mixed.rs, src/ops/strict.rs, src/ops/rounding.rs
Adds 21 mixed-signedness add/sub traits plus CheckedSignedDiff; adds 11 strict-arithmetic traits panicking in all profiles with explicit panic messages; adds DivCeil/DivFloor/DivExact/MultipleOf/NextMultipleOf/Midpoint with overflow-free signed/unsigned logic; all with comprehensive test coverage.
Logarithm, square root, power-of-two, and parity traits
src/ops/log.rs, src/ops/sqrt.rs, src/ops/pow2.rs, src/ops/parity.rs
Adds Ilog2/Ilog10/Ilog with panicking and checked variants; Isqrt/CheckedIsqrt delegating to primitives; IsPowerOfTwo predicate and NextPowerOfTwo with checked/wrapping variants; Parity trait with const blanket &T impl; all with unit tests.
Bit manipulation and integer conversion ops
src/ops/bits.rs, src/ops/convert.rs
Adds UnboundedShl/Shr with out-of-range fill, FunnelShl/Shr with overflow checks, ShlExact/ShrExact with reversibility checking, HighestOne/LowestOne indexing, IsolateHighestOne/IsolateLowestOne branchless operations, BitWidth, and DepositBits/ExtractBits PDEP/PEXT-style. Adds AbsDiff, UnsignedAbs, ClampMagnitude, CastSigned/Unsigned bit reinterpretation, Widen/Truncate with identities, and generic CheckedCast/StrictCast/WrappingCast/SaturatingCast for all primitive pairs; all with comprehensive tests.
Float ops, ASCII parsing, and format_into
src/ops/float_ops.rs, src/ops/from_ascii.rs, src/ops/format_into.rs
Adds FloatBits, NextUp/NextDown, Maximum/Minimum (IEEE 754-2019), RoundTiesEven banker's rounding, Algebraic rewrite-license ops, Erf/Gamma (libm-gated). Introduces AsciiErrorKind/AsciiParseError/FromAscii const trait for &[u8] parsing with radix validation. Adds nightly-only FormatInto mirroring std's NumBuffer decimal emission; all with extensive unit tests.
Constant-time masked-return ops and typestate proofs
src/ops/ct.rs, src/ops/typestate.rs
Adds constant-time predicates (CtIsZero, CtParity, CtIsPowerOfTwo) and checked arithmetic (CtCheckedAdd/Sub/Mul/Neg) returning subtle::Choice/CtOption. Introduces zero-runtime-cost typestate proofs: PowerOfTwo<T> (exponent-backed) with consuming PowerOfTwoOps; BitIndex<T> (index-backed) with BitIndexOps; HasNonZero/DivNonZero bridges; signed proofs NonNegative<T>, Positive<T>, NonMin<T> with refinement/narrowing and total ops; unsigned parity Odd<T>/Even<T> with TryFrom and const new under Parity bounds; float Finite<T> with total order impls. Under ct feature adds masked new_ct constructors and CtNonZero bridge; comprehensive test suite.
Module wiring, integration tests, and examples
src/ops/mod.rs, tests/const_nightly.rs, tests/prim_bits.rs, tests/typestate_generic_carrier.rs, examples/typestates.rs
Expands ops/mod.rs with 18 new pub module declarations and feature-gated exports; adds const_nightly.rs nightly integration test proving const-callability across all new ops families; adds prim_bits.rs proving PrimBits works on capability-restricted wrapper; adds typestate_generic_carrier.rs tripwire test for generic safe constructors; adds examples/typestates.rs demonstrating all typestate families and ct masked constructors; updates cast test imports and call sites.
Float refactoring and minor optimizations
src/float.rs, tests/cast.rs
Refactors FloatCore round/trunc/min/max to compact conditional expressions; updates FloatConst to delegate TAU/LOG10_2/LOG2_10 to $T::consts; updates float tests to use f32::NAN and core::f32/f64::consts::PI directly; reorganizes cast test imports and assertions.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • kaidokert/num-traits#6: Establishes the const-trait infrastructure (c0nst markers, const trait syntax) across the same crate modules (Cargo.toml, src/bounds.rs, src/cast.rs, src/int.rs, src/ops/*); this PR continues that work by refactoring method signatures to by-value with associated outputs and adding 15+ new operation modules throughout the ops namespace.

Poem

🐇 Hop hop, the traits go by-value today,
No more &self to borrow away!
PrimBits hops off, typestates appear,
Carry and clmul fill the frontier.
From ASCII bytes to CT proofs so tight—
const-num-traits shines with delight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main objectives of the PR: a const-trait port of num-traits with modern operation coverage and typestate support.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch squashy

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/ops/clmul.rs Outdated
Comment thread src/ops/clmul.rs Outdated
Comment thread src/ops/clmul.rs Outdated
Comment thread src/ops/clmul.rs Outdated
Comment thread src/ops/bits.rs Outdated
Comment thread src/ops/bits.rs Outdated
Comment thread src/ops/typestate.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/ops/clmul.rs Outdated
Comment thread src/lib.rs
Comment thread Cargo.toml
… 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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Change shift trait bounds from usize to u32 to match parameter types and return projections.

The WrappingShl and WrappingShr traits accept rhs: u32 but declare trait bounds and return type projections using Shl<usize> and Shr<usize>. For custom types where Shl<u32>::Output differs from Shl<usize>::Output, this mismatch advertises an incorrect return contract.

Update trait bounds, return projections, and Wrapping<T> where clauses from usize to u32:

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 | 🔵 Trivial

Remove 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 the core::* modules. The code only uses primitive associated constants like i8::MIN and i16::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

📥 Commits

Reviewing files that changed from the base of the PR and between 46ca76f and 3a3bc7e.

📒 Files selected for processing (38)
  • Cargo.toml
  • MIGRATION.md
  • README.md
  • src/bounds.rs
  • src/cast.rs
  • src/identities.rs
  • src/int.rs
  • src/lib.rs
  • src/ops/bits.rs
  • src/ops/bytes.rs
  • src/ops/carrying.rs
  • src/ops/checked.rs
  • src/ops/clmul.rs
  • src/ops/convert.rs
  • src/ops/ct.rs
  • src/ops/euclid.rs
  • src/ops/float_ops.rs
  • src/ops/format_into.rs
  • src/ops/from_ascii.rs
  • src/ops/inv.rs
  • src/ops/log.rs
  • src/ops/mixed.rs
  • src/ops/mod.rs
  • src/ops/mul_add.rs
  • src/ops/overflowing.rs
  • src/ops/parity.rs
  • src/ops/pow2.rs
  • src/ops/rounding.rs
  • src/ops/saturating.rs
  • src/ops/sqrt.rs
  • src/ops/strict.rs
  • src/ops/typestate.rs
  • src/ops/wrapping.rs
  • src/personality.rs
  • src/pow.rs
  • src/sign.rs
  • tests/const_nightly.rs
  • tests/prim_bits.rs

Comment thread MIGRATION.md Outdated
Comment thread README.md Outdated
Comment thread src/cast.rs
Comment thread src/int.rs Outdated
Comment thread src/ops/bits.rs Outdated
Comment thread src/ops/saturating.rs Outdated
Comment thread src/ops/typestate.rs
Comment thread src/ops/typestate.rs
Comment thread src/ops/wrapping.rs Outdated
Comment thread src/sign.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>
kaidokert and others added 9 commits June 22, 2026 22:07
…-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.
@kaidokert

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/cast.rs
Comment thread src/cast.rs
Comment thread src/cast.rs
Comment thread src/cast.rs
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.
The const-num-traits README rewrite is backed out of this PR temporarily.
The PR-version changes are preserved in README.stashed.patch (untracked,
re-appliable with `git apply`) and remain in history at 4405b73 / c1e7b47.
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.
@kaidokert
kaidokert merged commit 175ef95 into const_traits Jun 24, 2026
6 checks passed
@kaidokert
kaidokert deleted the squashy branch July 3, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant