Skip to main content

fixed_bigint/fixeduint/
bit_ops_impl.rs

1use super::{FixedUInt, MachineWord, const_shl_ct, const_shl_impl, const_shr_ct, const_shr_impl};
2
3use crate::machineword::ConstMachineWord;
4use const_num_traits::{
5    CheckedShl, CheckedShr, ConstZero, OverflowingShl, OverflowingShr, UnboundedShl, UnboundedShr,
6    WrappingShl, WrappingShr,
7};
8use const_num_traits::{Nct, Personality, PersonalityTag};
9
10c0nst::c0nst! {
11    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Not for FixedUInt<T, N, P> {
12        type Output = Self;
13        fn not(self) -> Self::Output {
14            let mut ret = <Self as ConstZero>::ZERO;
15            let mut i = 0;
16            while i < N {
17                ret.array[i] = !self.array[i];
18                i += 1;
19            }
20            ret
21        }
22    }
23
24    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Not for &FixedUInt<T, N, P> {
25        type Output = FixedUInt<T, N, P>;
26        fn not(self) -> Self::Output {
27            <FixedUInt<T, N, P> as core::ops::Not>::not(FixedUInt::from_array(self.array))
28        }
29    }
30
31    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd<&FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
32        type Output = FixedUInt<T, N, P>;
33        fn bitand(self, other: &FixedUInt<T, N, P>) -> Self::Output {
34            let mut ret = <FixedUInt<T, N, P> as ConstZero>::ZERO;
35            let mut i = 0;
36            while i < N {
37                ret.array[i] = self.array[i] & other.array[i];
38                i += 1;
39            }
40            ret
41        }
42    }
43
44    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd for FixedUInt<T, N, P> {
45        type Output = Self;
46        fn bitand(self, other: Self) -> Self::Output {
47            (&self).bitand(&other)
48        }
49    }
50
51    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
52        type Output = Self;
53        fn bitand(self, other: &FixedUInt<T, N, P>) -> Self::Output {
54            (&self).bitand(other)
55        }
56    }
57
58    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd<FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
59        type Output = FixedUInt<T, N, P>;
60        fn bitand(self, other: FixedUInt<T, N, P>) -> Self::Output {
61            self.bitand(&other)
62        }
63    }
64
65    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAndAssign for FixedUInt<T, N, P> {
66        fn bitand_assign(&mut self, other: Self) {
67            let mut i = 0;
68            while i < N {
69                self.array[i] &= other.array[i];
70                i += 1;
71            }
72        }
73    }
74
75    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAndAssign<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
76        fn bitand_assign(&mut self, other: &FixedUInt<T, N, P>) {
77            let mut i = 0;
78            while i < N {
79                self.array[i] &= other.array[i];
80                i += 1;
81            }
82        }
83    }
84
85    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr<&FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
86        type Output = FixedUInt<T, N, P>;
87        fn bitor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
88            let mut ret = <FixedUInt<T, N, P> as ConstZero>::ZERO;
89            let mut i = 0;
90            while i < N {
91                ret.array[i] = self.array[i] | other.array[i];
92                i += 1;
93            }
94            ret
95        }
96    }
97
98    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr for FixedUInt<T, N, P> {
99        type Output = Self;
100        fn bitor(self, other: Self) -> Self::Output {
101            (&self).bitor(&other)
102        }
103    }
104
105    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
106        type Output = Self;
107        fn bitor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
108            (&self).bitor(other)
109        }
110    }
111
112    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr<FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
113        type Output = FixedUInt<T, N, P>;
114        fn bitor(self, other: FixedUInt<T, N, P>) -> Self::Output {
115            self.bitor(&other)
116        }
117    }
118
119    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOrAssign for FixedUInt<T, N, P> {
120        fn bitor_assign(&mut self, other: Self) {
121            let mut i = 0;
122            while i < N {
123                self.array[i] |= other.array[i];
124                i += 1;
125            }
126        }
127    }
128
129    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOrAssign<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
130        fn bitor_assign(&mut self, other: &FixedUInt<T, N, P>) {
131            let mut i = 0;
132            while i < N {
133                self.array[i] |= other.array[i];
134                i += 1;
135            }
136        }
137    }
138
139    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor<&FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
140        type Output = FixedUInt<T, N, P>;
141        fn bitxor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
142            let mut ret = <FixedUInt<T, N, P> as ConstZero>::ZERO;
143            let mut i = 0;
144            while i < N {
145                ret.array[i] = self.array[i] ^ other.array[i];
146                i += 1;
147            }
148            ret
149        }
150    }
151
152    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor for FixedUInt<T, N, P> {
153        type Output = Self;
154        fn bitxor(self, other: Self) -> Self::Output {
155            (&self).bitxor(&other)
156        }
157    }
158
159    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
160        type Output = Self;
161        fn bitxor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
162            (&self).bitxor(other)
163        }
164    }
165
166    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor<FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
167        type Output = FixedUInt<T, N, P>;
168        fn bitxor(self, other: FixedUInt<T, N, P>) -> Self::Output {
169            self.bitxor(&other)
170        }
171    }
172
173    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXorAssign for FixedUInt<T, N, P> {
174        fn bitxor_assign(&mut self, other: Self) {
175            let mut i = 0;
176            while i < N {
177                self.array[i] ^= other.array[i];
178                i += 1;
179            }
180        }
181    }
182
183    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXorAssign<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
184        fn bitxor_assign(&mut self, other: &FixedUInt<T, N, P>) {
185            let mut i = 0;
186            while i < N {
187                self.array[i] ^= other.array[i];
188                i += 1;
189            }
190        }
191    }
192
193    // Primary Shl/Shr implementations
194    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<usize> for FixedUInt<T, N, P> {
195        type Output = Self;
196        fn shl(self, bits: usize) -> Self::Output {
197            let mut result = self;
198            match P::TAG {
199                PersonalityTag::Nct => const_shl_impl(&mut result, bits),
200                PersonalityTag::Ct => const_shl_ct(&mut result, bits),
201            }
202            result
203        }
204    }
205
206    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<usize> for FixedUInt<T, N, P> {
207        type Output = Self;
208        fn shr(self, bits: usize) -> Self::Output {
209            let mut result = self;
210            match P::TAG {
211                PersonalityTag::Nct => const_shr_impl(&mut result, bits),
212                PersonalityTag::Ct => const_shr_ct(&mut result, bits),
213            }
214            result
215        }
216    }
217
218    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<u32> for FixedUInt<T, N, P> {
219        type Output = Self;
220        fn shl(self, bits: u32) -> Self::Output {
221            const_unbounded_shl_u32(self, bits)
222        }
223    }
224
225    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<u32> for FixedUInt<T, N, P> {
226        type Output = Self;
227        fn shr(self, bits: u32) -> Self::Output {
228            const_unbounded_shr_u32(self, bits)
229        }
230    }
231
232    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&usize> for FixedUInt<T, N, P> {
233        type Output = Self;
234        fn shl(self, bits: &usize) -> Self::Output {
235            self.shl(*bits)
236        }
237    }
238
239    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&usize> for FixedUInt<T, N, P> {
240        type Output = Self;
241        fn shr(self, bits: &usize) -> Self::Output {
242            self.shr(*bits)
243        }
244    }
245
246    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&u32> for FixedUInt<T, N, P> {
247        type Output = Self;
248        fn shl(self, bits: &u32) -> Self::Output {
249            self.shl(*bits)
250        }
251    }
252
253    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&u32> for FixedUInt<T, N, P> {
254        type Output = Self;
255        fn shr(self, bits: &u32) -> Self::Output {
256            self.shr(*bits)
257        }
258    }
259
260    // Shl/Shr for &FixedUInt
261    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<usize> for &FixedUInt<T, N, P> {
262        type Output = FixedUInt<T, N, P>;
263        fn shl(self, bits: usize) -> Self::Output {
264            FixedUInt::from_array(self.array).shl(bits)
265        }
266    }
267
268    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<usize> for &FixedUInt<T, N, P> {
269        type Output = FixedUInt<T, N, P>;
270        fn shr(self, bits: usize) -> Self::Output {
271            FixedUInt::from_array(self.array).shr(bits)
272        }
273    }
274
275    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<u32> for &FixedUInt<T, N, P> {
276        type Output = FixedUInt<T, N, P>;
277        fn shl(self, bits: u32) -> Self::Output {
278            FixedUInt::from_array(self.array).shl(bits)
279        }
280    }
281
282    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<u32> for &FixedUInt<T, N, P> {
283        type Output = FixedUInt<T, N, P>;
284        fn shr(self, bits: u32) -> Self::Output {
285            FixedUInt::from_array(self.array).shr(bits)
286        }
287    }
288
289    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&usize> for &FixedUInt<T, N, P> {
290        type Output = FixedUInt<T, N, P>;
291        fn shl(self, bits: &usize) -> Self::Output {
292            FixedUInt::from_array(self.array).shl(*bits)
293        }
294    }
295
296    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&usize> for &FixedUInt<T, N, P> {
297        type Output = FixedUInt<T, N, P>;
298        fn shr(self, bits: &usize) -> Self::Output {
299            FixedUInt::from_array(self.array).shr(*bits)
300        }
301    }
302
303    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&u32> for &FixedUInt<T, N, P> {
304        type Output = FixedUInt<T, N, P>;
305        fn shl(self, bits: &u32) -> Self::Output {
306            FixedUInt::from_array(self.array).shl(*bits)
307        }
308    }
309
310    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&u32> for &FixedUInt<T, N, P> {
311        type Output = FixedUInt<T, N, P>;
312        fn shr(self, bits: &u32) -> Self::Output {
313            FixedUInt::from_array(self.array).shr(*bits)
314        }
315    }
316
317    // ShlAssign/ShrAssign
318    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShlAssign<usize> for FixedUInt<T, N, P> {
319        fn shl_assign(&mut self, bits: usize) {
320            match P::TAG {
321                PersonalityTag::Nct => const_shl_impl(self, bits),
322                PersonalityTag::Ct => const_shl_ct(self, bits),
323            }
324        }
325    }
326
327    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShrAssign<usize> for FixedUInt<T, N, P> {
328        fn shr_assign(&mut self, bits: usize) {
329            match P::TAG {
330                PersonalityTag::Nct => const_shr_impl(self, bits),
331                PersonalityTag::Ct => const_shr_ct(self, bits),
332            }
333        }
334    }
335
336    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShlAssign<&usize> for FixedUInt<T, N, P> {
337        fn shl_assign(&mut self, bits: &usize) {
338            match P::TAG {
339                PersonalityTag::Nct => const_shl_impl(self, *bits),
340                PersonalityTag::Ct => const_shl_ct(self, *bits),
341            }
342        }
343    }
344
345    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShrAssign<&usize> for FixedUInt<T, N, P> {
346        fn shr_assign(&mut self, bits: &usize) {
347            match P::TAG {
348                PersonalityTag::Nct => const_shr_impl(self, *bits),
349                PersonalityTag::Ct => const_shr_ct(self, *bits),
350            }
351        }
352    }
353
354    // `u32`/`&u32` assign forms route through the same over-width-collapsing
355    // helper as `Shl<u32>`/`Shr<u32>`, so `x <<= n` and `x = x << n` agree.
356    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShlAssign<u32> for FixedUInt<T, N, P> {
357        fn shl_assign(&mut self, bits: u32) {
358            *self = const_unbounded_shl_u32(*self, bits);
359        }
360    }
361
362    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShrAssign<u32> for FixedUInt<T, N, P> {
363        fn shr_assign(&mut self, bits: u32) {
364            *self = const_unbounded_shr_u32(*self, bits);
365        }
366    }
367
368    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShlAssign<&u32> for FixedUInt<T, N, P> {
369        fn shl_assign(&mut self, bits: &u32) {
370            *self = const_unbounded_shl_u32(*self, *bits);
371        }
372    }
373
374    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShrAssign<&u32> for FixedUInt<T, N, P> {
375        fn shr_assign(&mut self, bits: &u32) {
376            *self = const_unbounded_shr_u32(*self, *bits);
377        }
378    }
379
380    // Shared body for `Shl<u32>` and `UnboundedShl::unbounded_shl`:
381    // shift by a u32 amount, with values outside [0, BIT_SIZE) collapsing
382    // to zero. Centralizing keeps the two entry points in sync.
383    pub(crate) c0nst fn const_unbounded_shl_u32<
384        T: [c0nst] ConstMachineWord + MachineWord,
385        const N: usize,
386        P: Personality,
387    >(
388        target: FixedUInt<T, N, P>,
389        bits: u32,
390    ) -> FixedUInt<T, N, P> {
391        match P::TAG {
392            PersonalityTag::Nct => {
393                let (shift, overflow) =
394                    normalize_shift_amount(bits, FixedUInt::<T, N, P>::BIT_SIZE);
395                if overflow {
396                    <FixedUInt<T, N, P> as ConstZero>::ZERO
397                } else {
398                    target << shift
399                }
400            }
401            PersonalityTag::Ct => {
402                // Skip `normalize_shift_amount` entirely. Its `if bits >=
403                // bit_size_u32` is a tainted-flag branch and `bits %
404                // bit_size_u32` is a variable-time modulo when bits is a
405                // secret — both leaks even though current LLVM may pattern-
406                // match them on power-of-2 BIT_SIZE. `const_shl_ct`'s
407                // barrel shifter already collapses out-of-range shifts to
408                // zero (via `const_shl_impl`'s `nwords >= N` zero-out), so
409                // the overflow detection is redundant for the Ct path —
410                // EXCEPT for the cast to usize on 16-bit-usize targets,
411                // where `bits as usize` truncates and could undo the
412                // saturation. Cap branchlessly to `BIT_SIZE` first; for
413                // every priority diagonal BIT_SIZE fits in u16, so the
414                // capped value casts losslessly even on AVR.
415                let bit_size_u32 = FixedUInt::<T, N, P>::BIT_SIZE as u32;
416                let capped = const_ct_min_u32(bits, bit_size_u32);
417                target << (capped as usize)
418            }
419        }
420    }
421
422    /// Mirror of [`const_unbounded_shl_u32`] for right-shifts.
423    pub(crate) c0nst fn const_unbounded_shr_u32<
424        T: [c0nst] ConstMachineWord + MachineWord,
425        const N: usize,
426        P: Personality,
427    >(
428        target: FixedUInt<T, N, P>,
429        bits: u32,
430    ) -> FixedUInt<T, N, P> {
431        match P::TAG {
432            PersonalityTag::Nct => {
433                let (shift, overflow) =
434                    normalize_shift_amount(bits, FixedUInt::<T, N, P>::BIT_SIZE);
435                if overflow {
436                    <FixedUInt<T, N, P> as ConstZero>::ZERO
437                } else {
438                    target >> shift
439                }
440            }
441            PersonalityTag::Ct => {
442                // See `const_unbounded_shl_u32` for why this skips
443                // `normalize_shift_amount` and caps before casting.
444                let bit_size_u32 = FixedUInt::<T, N, P>::BIT_SIZE as u32;
445                let capped = const_ct_min_u32(bits, bit_size_u32);
446                target >> (capped as usize)
447            }
448        }
449    }
450
451    /// Branchless CT-safe `min(bits, cap)` for u32, used to clamp Ct
452    /// shift amounts to `BIT_SIZE` before casting to usize. The
453    /// `black_box` on the mask is required — without it, LLVM
454    /// recognises the XOR-AND-XOR select idiom and rewrites it into a
455    /// `cmov` whose flag depends on the secret `bits`. Same defence as
456    /// `const_ct_select` (PR #118).
457    c0nst fn const_ct_min_u32(bits: u32, cap: u32) -> u32 {
458        // diff = cap - bits, wraps to negative (high bit set) iff bits > cap.
459        let diff = cap.wrapping_sub(bits);
460        let too_big_bit = (diff >> 31) & 1;
461        let too_big_mask = core::hint::black_box(too_big_bit.wrapping_neg());
462        // bits if !too_big, cap otherwise. XOR-AND-XOR select with
463        // opaque mask.
464        bits ^ (too_big_mask & (bits ^ cap))
465    }
466
467    // Helper to normalize shift amount and detect overflow.
468    // Handles both 16-bit (usize < u32) and 64-bit (bit_size > u32::MAX) platforms.
469    c0nst fn normalize_shift_amount(bits: u32, bit_size: usize) -> (usize, bool) {
470        let bit_size_u32 = bit_size as u32;
471        if bit_size == 0 {
472            // Zero-size type: always overflow
473            (0, true)
474        } else if bit_size_u32 == 0 {
475            // bit_size is a non-zero multiple of 2^32 (huge type on 64-bit).
476            // Since bits is u32, it's always smaller than bit_size. No overflow.
477            (bits as usize, false)
478        } else if bits >= bit_size_u32 {
479            // Normal case: shift exceeds bit width
480            ((bits % bit_size_u32) as usize, true)
481        } else {
482            (bits as usize, false)
483        }
484    }
485
486    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShl for FixedUInt<T, N, P> {
487        type Output = FixedUInt<T, N, P>;
488        fn overflowing_shl(self, bits: u32) -> (Self, bool) {
489            let (shift, overflow) = normalize_shift_amount(bits, Self::BIT_SIZE);
490            let res = core::ops::Shl::<usize>::shl(self, shift);
491            (res, overflow)
492        }
493    }
494
495    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShr for FixedUInt<T, N, P> {
496        type Output = FixedUInt<T, N, P>;
497        fn overflowing_shr(self, bits: u32) -> (Self, bool) {
498            let (shift, overflow) = normalize_shift_amount(bits, Self::BIT_SIZE);
499            let res = core::ops::Shr::<usize>::shr(self, shift);
500            (res, overflow)
501        }
502    }
503
504    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShl for FixedUInt<T, N, P> {
505        type Output = FixedUInt<T, N, P>;
506        fn wrapping_shl(self, bits: u32) -> Self {
507            OverflowingShl::overflowing_shl(self, bits).0
508        }
509    }
510
511    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShr for FixedUInt<T, N, P> {
512        type Output = FixedUInt<T, N, P>;
513        fn wrapping_shr(self, bits: u32) -> Self {
514            OverflowingShr::overflowing_shr(self, bits).0
515        }
516    }
517
518    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShl for FixedUInt<T, N, P> {
519        type Output = FixedUInt<T, N, P>;
520        fn checked_shl(self, bits: u32) -> Option<Self> {
521            let (res, overflow) = OverflowingShl::overflowing_shl(self, bits);
522            if overflow { None } else { Some(res) }
523        }
524    }
525
526    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShr for FixedUInt<T, N, P> {
527        type Output = FixedUInt<T, N, P>;
528        fn checked_shr(self, bits: u32) -> Option<Self> {
529            let (res, overflow) = OverflowingShr::overflowing_shr(self, bits);
530            if overflow { None } else { Some(res) }
531        }
532    }
533
534    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShl for FixedUInt<T, N, P> {
535        type Output = FixedUInt<T, N, P>;
536        fn unbounded_shl(self, rhs: u32) -> Self {
537            const_unbounded_shl_u32(self, rhs)
538        }
539    }
540
541    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShr for FixedUInt<T, N, P> {
542        type Output = FixedUInt<T, N, P>;
543        fn unbounded_shr(self, rhs: u32) -> Self {
544            const_unbounded_shr_u32(self, rhs)
545        }
546    }
547
548    // --- Reference-receiver shift impls (see add_sub_impl.rs for rationale) ---
549    //
550    // Output comes from the operator supertrait (`Shl<u32>` / `Shr<u32>`
551    // for `&FixedUInt`, defined earlier in this c0nst! block), so
552    // Output resolves to `FixedUInt<T,N,P>`.
553
554    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShl for &FixedUInt<T, N, P> {
555        type Output = FixedUInt<T, N, P>;
556        fn overflowing_shl(self, bits: u32) -> (FixedUInt<T, N, P>, bool) {
557            <FixedUInt<T, N, P> as OverflowingShl>::overflowing_shl(FixedUInt::from_array(self.array), bits)
558        }
559    }
560
561    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShr for &FixedUInt<T, N, P> {
562        type Output = FixedUInt<T, N, P>;
563        fn overflowing_shr(self, bits: u32) -> (FixedUInt<T, N, P>, bool) {
564            <FixedUInt<T, N, P> as OverflowingShr>::overflowing_shr(FixedUInt::from_array(self.array), bits)
565        }
566    }
567
568    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShl for &FixedUInt<T, N, P> {
569        type Output = FixedUInt<T, N, P>;
570        fn wrapping_shl(self, bits: u32) -> FixedUInt<T, N, P> {
571            <FixedUInt<T, N, P> as WrappingShl>::wrapping_shl(FixedUInt::from_array(self.array), bits)
572        }
573    }
574
575    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShr for &FixedUInt<T, N, P> {
576        type Output = FixedUInt<T, N, P>;
577        fn wrapping_shr(self, bits: u32) -> FixedUInt<T, N, P> {
578            <FixedUInt<T, N, P> as WrappingShr>::wrapping_shr(FixedUInt::from_array(self.array), bits)
579        }
580    }
581
582    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShl for &FixedUInt<T, N, P> {
583        type Output = FixedUInt<T, N, P>;
584        fn checked_shl(self, bits: u32) -> Option<FixedUInt<T, N, P>> {
585            <FixedUInt<T, N, P> as CheckedShl>::checked_shl(FixedUInt::from_array(self.array), bits)
586        }
587    }
588
589    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShr for &FixedUInt<T, N, P> {
590        type Output = FixedUInt<T, N, P>;
591        fn checked_shr(self, bits: u32) -> Option<FixedUInt<T, N, P>> {
592            <FixedUInt<T, N, P> as CheckedShr>::checked_shr(FixedUInt::from_array(self.array), bits)
593        }
594    }
595
596    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShl for &FixedUInt<T, N, P> {
597        type Output = FixedUInt<T, N, P>;
598        fn unbounded_shl(self, rhs: u32) -> FixedUInt<T, N, P> {
599            <FixedUInt<T, N, P> as UnboundedShl>::unbounded_shl(FixedUInt::from_array(self.array), rhs)
600        }
601    }
602
603    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShr for &FixedUInt<T, N, P> {
604        type Output = FixedUInt<T, N, P>;
605        fn unbounded_shr(self, rhs: u32) -> FixedUInt<T, N, P> {
606            <FixedUInt<T, N, P> as UnboundedShr>::unbounded_shr(FixedUInt::from_array(self.array), rhs)
607        }
608    }
609
610    // --- HighestOne / LowestOne ---------------------------------------------
611    //
612    // Indices of the highest / lowest set bit. Both reduce to the leading-
613    // and trailing-zero counts we already compute (Nct fast-path through
614    // `const_leading_zeros` / `const_trailing_zeros`; Ct path through the
615    // mask-select `_ct` variants).
616    //
617    // NOTE: NOT constant-time on a `FixedUInt<_, _, Ct>` carrier — the
618    // `Option` return shape leaks whether `self == 0` regardless of what
619    // the caller does with the value. Ct callers whose input might be
620    // zero should mask it separately (`CtIsZero::ct_is_zero`) before
621    // consulting these methods, or avoid them entirely.
622
623    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::HighestOne for FixedUInt<T, N, P> {
624        fn highest_one(self) -> Option<u32> {
625            let lz = <Self as const_num_traits::PrimBits>::leading_zeros(self);
626            if lz as usize == Self::BIT_SIZE {
627                None
628            } else {
629                Some(Self::BIT_SIZE as u32 - 1 - lz)
630            }
631        }
632    }
633
634    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::LowestOne for FixedUInt<T, N, P> {
635        fn lowest_one(self) -> Option<u32> {
636            let tz = <Self as const_num_traits::PrimBits>::trailing_zeros(self);
637            if tz as usize == Self::BIT_SIZE {
638                None
639            } else {
640                Some(tz)
641            }
642        }
643    }
644
645    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::HighestOne for &FixedUInt<T, N, P> {
646        fn highest_one(self) -> Option<u32> {
647            <FixedUInt<T, N, P> as const_num_traits::HighestOne>::highest_one(FixedUInt::from_array(self.array))
648        }
649    }
650
651    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::LowestOne for &FixedUInt<T, N, P> {
652        fn lowest_one(self) -> Option<u32> {
653            <FixedUInt<T, N, P> as const_num_traits::LowestOne>::lowest_one(FixedUInt::from_array(self.array))
654        }
655    }
656
657    // --- BitWidth ----------------------------------------------------------
658    //
659    // Minimum bits to represent self: `BIT_SIZE - leading_zeros(self)`.
660    // Returns 0 for 0. Mirrors std's `u32::BITS - n.leading_zeros()`.
661
662    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitWidth for FixedUInt<T, N, P> {
663        fn bit_width(self) -> u32 {
664            Self::BIT_SIZE as u32 - <Self as const_num_traits::PrimBits>::leading_zeros(self)
665        }
666    }
667
668    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitWidth for &FixedUInt<T, N, P> {
669        fn bit_width(self) -> u32 {
670            <FixedUInt<T, N, P> as const_num_traits::BitWidth>::bit_width(FixedUInt::from_array(self.array))
671        }
672    }
673
674    // --- BitsPrecision -----------------------------------------------------
675    //
676    // Operating width: for a fixed carrier this is `BIT_SIZE` (= N·word_bits),
677    // value-independent — width == capacity for FixedUInt. Contrast BitWidth
678    // (bit-length, per-value).
679
680    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitsPrecision for FixedUInt<T, N, P> {
681        fn bits_precision(&self) -> u32 {
682            Self::BIT_SIZE as u32
683        }
684    }
685
686    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitsPrecision for &FixedUInt<T, N, P> {
687        fn bits_precision(&self) -> u32 {
688            FixedUInt::<T, N, P>::BIT_SIZE as u32
689        }
690    }
691
692    // --- WithPrecision -----------------------------------------------------
693    //
694    // The operating width is the type (N words), so widening is the identity —
695    // same as the primitive impls.
696
697    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::WithPrecision for FixedUInt<T, N, P> {
698        fn widen_to_precision(self, _bits_precision: u32) -> Self {
699            self
700        }
701    }
702
703    // --- IsolateHighestOne / IsolateLowestOne ------------------------------
704    //
705    // Mask the value down to just its highest / lowest set bit.
706    // IsolateHighestOne: `0` → `0`, else `1 << (BIT_SIZE - 1 - leading_zeros)`.
707    // IsolateLowestOne: the classic `self & self.wrapping_neg()` trick
708    //   (which yields 0 for 0 input automatically) — and uses arithmetic
709    //   already implemented uniformly across personalities.
710
711    // NOTE: `isolate_highest_one` is NOT constant-time for FixedUInt under
712    // Ct. The `if lz as usize == Self::BIT_SIZE` branch is value-dependent
713    // (it leaks whether `self == 0`), and the `pos`-parameterized shift's
714    // bit-count is value-dependent too. `IsolateLowestOne` below uses the
715    // `self & (0 - self)` trick and IS branchless; callers needing a
716    // constant-time highest-bit isolation on a Ct carrier should mask
717    // through a different path.
718    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateHighestOne for FixedUInt<T, N, P> {
719        type Output = Self;
720        fn isolate_highest_one(self) -> Self {
721            let lz = <Self as const_num_traits::PrimBits>::leading_zeros(self);
722            if lz as usize == Self::BIT_SIZE {
723                // self == 0; preserve the zero.
724                <Self as const_num_traits::ConstZero>::ZERO
725            } else {
726                let pos = Self::BIT_SIZE as u32 - 1 - lz;
727                <Self as const_num_traits::ConstOne>::ONE << (pos as usize)
728            }
729        }
730    }
731
732    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateLowestOne for FixedUInt<T, N, P> {
733        type Output = Self;
734        fn isolate_lowest_one(self) -> Self {
735            // `self & (-self)`. For unsigned `-x` is `wrapping_neg(x) =
736            // (0).wrapping_sub(x)`. Works for `self == 0` (0 & 0 = 0).
737            let neg = <Self as const_num_traits::WrappingSub>::wrapping_sub(
738                <Self as const_num_traits::ConstZero>::ZERO,
739                self,
740            );
741            self & neg
742        }
743    }
744
745    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateHighestOne for &FixedUInt<T, N, P> {
746        type Output = FixedUInt<T, N, P>;
747        fn isolate_highest_one(self) -> FixedUInt<T, N, P> {
748            <FixedUInt<T, N, P> as const_num_traits::IsolateHighestOne>::isolate_highest_one(FixedUInt::from_array(self.array))
749        }
750    }
751
752    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateLowestOne for &FixedUInt<T, N, P> {
753        type Output = FixedUInt<T, N, P>;
754        fn isolate_lowest_one(self) -> FixedUInt<T, N, P> {
755            <FixedUInt<T, N, P> as const_num_traits::IsolateLowestOne>::isolate_lowest_one(FixedUInt::from_array(self.array))
756        }
757    }
758
759    // --- ShlExact / ShrExact -----------------------------------------------
760    //
761    // Reversible (lossless) shifts: return `None` if any one-bit would be
762    // shifted out, or `rhs >= BIT_SIZE`. Mirrors core's primitive impls
763    // exactly (compare `rhs` against `leading_zeros` / `trailing_zeros`).
764    //
765    // NOTE: NOT constant-time on a `FixedUInt<_, _, Ct>` carrier — the
766    // `Option` return shape leaks a range predicate on
767    // `leading_zeros(self)` / `trailing_zeros(self)`, i.e. the bit-width
768    // of the secret. Ct callers wanting exact shifts should either avoid
769    // this trait or gate on their own precondition before calling.
770
771    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShlExact for FixedUInt<T, N, P> {
772        type Output = FixedUInt<T, N, P>;
773        fn shl_exact(self, rhs: u32) -> Option<Self> {
774            if (rhs as usize) < Self::BIT_SIZE
775                && rhs <= <Self as const_num_traits::PrimBits>::leading_zeros(self)
776            {
777                Some(self << (rhs as usize))
778            } else {
779                None
780            }
781        }
782    }
783
784    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShrExact for FixedUInt<T, N, P> {
785        type Output = FixedUInt<T, N, P>;
786        fn shr_exact(self, rhs: u32) -> Option<Self> {
787            if (rhs as usize) < Self::BIT_SIZE
788                && rhs <= <Self as const_num_traits::PrimBits>::trailing_zeros(self)
789            {
790                Some(self >> (rhs as usize))
791            } else {
792                None
793            }
794        }
795    }
796
797    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShlExact for &FixedUInt<T, N, P> {
798        type Output = FixedUInt<T, N, P>;
799        fn shl_exact(self, rhs: u32) -> Option<FixedUInt<T, N, P>> {
800            <FixedUInt<T, N, P> as const_num_traits::ShlExact>::shl_exact(FixedUInt::from_array(self.array), rhs)
801        }
802    }
803
804    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShrExact for &FixedUInt<T, N, P> {
805        type Output = FixedUInt<T, N, P>;
806        fn shr_exact(self, rhs: u32) -> Option<FixedUInt<T, N, P>> {
807            <FixedUInt<T, N, P> as const_num_traits::ShrExact>::shr_exact(FixedUInt::from_array(self.array), rhs)
808        }
809    }
810
811    // --- FunnelShl / FunnelShr ---------------------------------------------
812    //
813    // Double-width funnel shift: form the conceptual `(hi, lo)` value of
814    // width `2 * BIT_SIZE`, shift by `n`, and return one half. `n` is a
815    // public parameter (loop counters, fixed amounts), so the `n >= BIT_SIZE`
816    // panic is value-independent and safe for both personalities. The shift
817    // ops dispatched by `<<` / `>>` are personality-aware (Ct uses the
818    // mask-AND-XOR variant), so the funnel impl inherits that.
819
820    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShl for FixedUInt<T, N, P> {
821        type Output = Self;
822        fn funnel_shl(self, rhs: Self, n: u32) -> Self {
823            assert!((n as usize) < Self::BIT_SIZE, "FixedUInt::funnel_shl: n out of range");
824            if n == 0 {
825                self
826            } else {
827                let lo_shift = Self::BIT_SIZE as u32 - n;
828                (self << (n as usize)) | (rhs >> (lo_shift as usize))
829            }
830        }
831    }
832
833    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShr for FixedUInt<T, N, P> {
834        type Output = Self;
835        fn funnel_shr(self, rhs: Self, n: u32) -> Self {
836            assert!((n as usize) < Self::BIT_SIZE, "FixedUInt::funnel_shr: n out of range");
837            if n == 0 {
838                rhs
839            } else {
840                let hi_shift = Self::BIT_SIZE as u32 - n;
841                (rhs >> (n as usize)) | (self << (hi_shift as usize))
842            }
843        }
844    }
845
846    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShl for &FixedUInt<T, N, P> {
847        type Output = FixedUInt<T, N, P>;
848        fn funnel_shl(self, rhs: Self, n: u32) -> FixedUInt<T, N, P> {
849            <FixedUInt<T, N, P> as const_num_traits::FunnelShl>::funnel_shl(FixedUInt::from_array(self.array), FixedUInt::from_array(rhs.array), n)
850        }
851    }
852
853    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShr for &FixedUInt<T, N, P> {
854        type Output = FixedUInt<T, N, P>;
855        fn funnel_shr(self, rhs: Self, n: u32) -> FixedUInt<T, N, P> {
856            <FixedUInt<T, N, P> as const_num_traits::FunnelShr>::funnel_shr(FixedUInt::from_array(self.array), FixedUInt::from_array(rhs.array), n)
857        }
858    }
859
860    // --- DepositBits / ExtractBits (PDEP / PEXT) ---------------------------
861    //
862    // Nct-only: the natural implementation iterates once per set bit of the
863    // mask, which is value-dependent. A constant-time version would have to
864    // iterate `BIT_SIZE` times unconditionally (and mask-select per step),
865    // which is a worthwhile but separate Ct-fixture exercise. For now we
866    // gate on `P = Nct`, matching how `CheckedDiv`/`CheckedRem` and the
867    // `Strict*` family are gated.
868
869    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::DepositBits for FixedUInt<T, N, Nct> {
870        type Output = Self;
871        fn deposit_bits(self, mask: Self) -> Self {
872            // Scatter contiguous low bits of `self` into positions of the
873            // one-bits of `mask`. Iterates once per set bit of `mask`.
874            let mut result = <Self as const_num_traits::ConstZero>::ZERO;
875            let mut remaining = mask;
876            let mut bb = <Self as const_num_traits::ConstOne>::ONE;
877            while !<Self as const_num_traits::Zero>::is_zero(&remaining) {
878                // Lowest set bit of `remaining` via `x & -x`.
879                let lowest = <Self as const_num_traits::IsolateLowestOne>::isolate_lowest_one(remaining);
880                if !<Self as const_num_traits::Zero>::is_zero(&(self & bb)) {
881                    result |= lowest;
882                }
883                remaining = remaining & <Self as const_num_traits::WrappingSub>::wrapping_sub(
884                    remaining,
885                    <Self as const_num_traits::ConstOne>::ONE,
886                );
887                bb = <Self as const_num_traits::WrappingShl>::wrapping_shl(bb, 1);
888            }
889            result
890        }
891    }
892
893    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::ExtractBits for FixedUInt<T, N, Nct> {
894        type Output = Self;
895        fn extract_bits(self, mask: Self) -> Self {
896            // Gather the bits of `self` selected by `mask` into the low end
897            // of the result. Mirror of `deposit_bits`.
898            let mut result = <Self as const_num_traits::ConstZero>::ZERO;
899            let mut remaining = mask;
900            let mut bb = <Self as const_num_traits::ConstOne>::ONE;
901            while !<Self as const_num_traits::Zero>::is_zero(&remaining) {
902                let lowest = <Self as const_num_traits::IsolateLowestOne>::isolate_lowest_one(remaining);
903                if !<Self as const_num_traits::Zero>::is_zero(&(self & lowest)) {
904                    result |= bb;
905                }
906                remaining = remaining & <Self as const_num_traits::WrappingSub>::wrapping_sub(
907                    remaining,
908                    <Self as const_num_traits::ConstOne>::ONE,
909                );
910                bb = <Self as const_num_traits::WrappingShl>::wrapping_shl(bb, 1);
911            }
912            result
913        }
914    }
915
916    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::DepositBits for &FixedUInt<T, N, Nct> {
917        type Output = FixedUInt<T, N, Nct>;
918        fn deposit_bits(self, mask: Self) -> FixedUInt<T, N, Nct> {
919            <FixedUInt<T, N, Nct> as const_num_traits::DepositBits>::deposit_bits(FixedUInt::from_array(self.array), FixedUInt::from_array(mask.array))
920        }
921    }
922
923    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::ExtractBits for &FixedUInt<T, N, Nct> {
924        type Output = FixedUInt<T, N, Nct>;
925        fn extract_bits(self, mask: Self) -> FixedUInt<T, N, Nct> {
926            <FixedUInt<T, N, Nct> as const_num_traits::ExtractBits>::extract_bits(FixedUInt::from_array(self.array), FixedUInt::from_array(mask.array))
927        }
928    }
929}
930
931// num_traits wrappers - delegate to const impls
932#[cfg(feature = "num-traits")]
933impl<T: MachineWord, const N: usize, P: Personality> num_traits::WrappingShl
934    for FixedUInt<T, N, P>
935{
936    fn wrapping_shl(&self, bits: u32) -> Self {
937        <&Self as WrappingShl>::wrapping_shl(self, bits)
938    }
939}
940
941#[cfg(feature = "num-traits")]
942impl<T: MachineWord, const N: usize, P: Personality> num_traits::WrappingShr
943    for FixedUInt<T, N, P>
944{
945    fn wrapping_shr(&self, bits: u32) -> Self {
946        <&Self as WrappingShr>::wrapping_shr(self, bits)
947    }
948}
949
950#[cfg(feature = "num-traits")]
951impl<T: MachineWord, const N: usize, P: Personality> num_traits::CheckedShl for FixedUInt<T, N, P> {
952    fn checked_shl(&self, bits: u32) -> Option<Self> {
953        <&Self as CheckedShl>::checked_shl(self, bits)
954    }
955}
956
957#[cfg(feature = "num-traits")]
958impl<T: MachineWord, const N: usize, P: Personality> num_traits::CheckedShr for FixedUInt<T, N, P> {
959    fn checked_shr(&self, bits: u32) -> Option<Self> {
960        <&Self as CheckedShr>::checked_shr(self, bits)
961    }
962}
963
964#[cfg(test)]
965// Coverage tests deliberately exercise every ref/value combination of
966// the bitwise/shift operators (see `test_*_combinations`).
967#[allow(clippy::op_ref)]
968mod tests {
969    use super::*;
970
971    #[test]
972    fn test_bitand_combinations() {
973        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
974        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
975        let expected = FixedUInt::<u8, 2>::from(8u8); // 1000
976
977        // value & value
978        assert_eq!(a & b, expected);
979        // value & ref
980        assert_eq!(a & &b, expected);
981        // ref & value
982        assert_eq!(&a & b, expected);
983        // ref & ref
984        assert_eq!(&a & &b, expected);
985    }
986
987    #[test]
988    fn test_bitor_combinations() {
989        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
990        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
991        let expected = FixedUInt::<u8, 2>::from(14u8); // 1110
992
993        // value | value
994        assert_eq!(a | b, expected);
995        // value | ref
996        assert_eq!(a | &b, expected);
997        // ref | value
998        assert_eq!(&a | b, expected);
999        // ref | ref
1000        assert_eq!(&a | &b, expected);
1001    }
1002
1003    #[test]
1004    fn test_bitxor_combinations() {
1005        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
1006        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
1007        let expected = FixedUInt::<u8, 2>::from(6u8); // 0110
1008
1009        // value ^ value
1010        assert_eq!(a ^ b, expected);
1011        // value ^ ref
1012        assert_eq!(a ^ &b, expected);
1013        // ref ^ value
1014        assert_eq!(&a ^ b, expected);
1015        // ref ^ ref
1016        assert_eq!(&a ^ &b, expected);
1017    }
1018
1019    #[test]
1020    fn test_shl_combinations() {
1021        let a = FixedUInt::<u8, 2>::from(2u8); // 0010
1022        let shift: usize = 2;
1023        let expected = FixedUInt::<u8, 2>::from(8u8); // 1000
1024
1025        // value << value
1026        assert_eq!(a << shift, expected);
1027        // value << ref
1028        assert_eq!(a << &shift, expected);
1029        // ref << value
1030        assert_eq!(&a << shift, expected);
1031        // ref << ref
1032        assert_eq!(&a << &shift, expected);
1033
1034        // Same with u32
1035        let shift32: u32 = 2;
1036        assert_eq!(a << shift32, expected);
1037        assert_eq!(a << &shift32, expected);
1038        assert_eq!(&a << shift32, expected);
1039        assert_eq!(&a << &shift32, expected);
1040    }
1041
1042    #[test]
1043    fn test_shr_combinations() {
1044        let a = FixedUInt::<u8, 2>::from(8u8); // 1000
1045        let shift: usize = 2;
1046        let expected = FixedUInt::<u8, 2>::from(2u8); // 0010
1047
1048        // value >> value
1049        assert_eq!(a >> shift, expected);
1050        // value >> ref
1051        assert_eq!(a >> &shift, expected);
1052        // ref >> value
1053        assert_eq!(&a >> shift, expected);
1054        // ref >> ref
1055        assert_eq!(&a >> &shift, expected);
1056
1057        // Same with u32
1058        let shift32: u32 = 2;
1059        assert_eq!(a >> shift32, expected);
1060        assert_eq!(a >> &shift32, expected);
1061        assert_eq!(&a >> shift32, expected);
1062        assert_eq!(&a >> &shift32, expected);
1063    }
1064
1065    #[test]
1066    fn test_const_bitops() {
1067        type TestInt = FixedUInt<u8, 2>;
1068
1069        let a = TestInt::from(0b11001100u8);
1070        let b = TestInt::from(0b10101010u8);
1071
1072        // Test not
1073        let not_a = !a;
1074        assert_eq!(not_a.array[0], 0b00110011);
1075        assert_eq!(not_a.array[1], 0xFF);
1076
1077        // Test bitand
1078        assert_eq!(a & b, TestInt::from(0b10001000u8));
1079
1080        // Test bitor
1081        assert_eq!(a | b, TestInt::from(0b11101110u8));
1082
1083        // Test bitxor
1084        assert_eq!(a ^ b, TestInt::from(0b01100110u8));
1085
1086        // Test shl
1087        assert_eq!(TestInt::from(1u8) << 4usize, TestInt::from(16u8));
1088
1089        // Test shr
1090        assert_eq!(TestInt::from(16u8) >> 2usize, TestInt::from(4u8));
1091
1092        #[cfg(feature = "nightly")]
1093        {
1094            const A: TestInt = FixedUInt::from_array([0b11001100, 0]);
1095            const B: TestInt = FixedUInt::from_array([0b10101010, 0]);
1096
1097            const NOT_A: TestInt = !A;
1098            const AND_AB: TestInt = A & B;
1099            const OR_AB: TestInt = A | B;
1100            const XOR_AB: TestInt = A ^ B;
1101            const SHL_1: TestInt = FixedUInt::from_array([1u8, 0]) << 4usize;
1102            const SHR_16: TestInt = FixedUInt::from_array([16u8, 0]) >> 2usize;
1103
1104            assert_eq!(NOT_A.array[0], 0b00110011);
1105            assert_eq!(AND_AB.array[0], 0b10001000);
1106            assert_eq!(OR_AB.array[0], 0b11101110);
1107            assert_eq!(XOR_AB.array[0], 0b01100110);
1108            assert_eq!(SHL_1.array[0], 16);
1109            assert_eq!(SHR_16.array[0], 4);
1110        }
1111    }
1112
1113    #[test]
1114    fn test_const_shift_traits() {
1115        type TestInt = FixedUInt<u8, 2>; // 16-bit
1116
1117        // Test overflowing_shl
1118        let a = TestInt::from(0x80u8); // 0x0080
1119        let (res, overflow) = OverflowingShl::overflowing_shl(a, 8);
1120        assert_eq!(res.array, [0, 0x80]); // 0x8000
1121        assert!(!overflow);
1122
1123        let (res, overflow) = OverflowingShl::overflowing_shl(a, 16);
1124        assert_eq!(res.array, [0x80, 0]); // wraps around
1125        assert!(overflow);
1126
1127        let (res, overflow) = OverflowingShl::overflowing_shl(a, 9);
1128        assert_eq!(res.array, [0, 0]); // high bits shifted out (but shift < bit_width)
1129        assert!(!overflow); // 9 < 16, so no overflow
1130
1131        // Test overflowing_shr
1132        let b = TestInt::from(0x0100u16); // 0x0100
1133        let (res, overflow) = OverflowingShr::overflowing_shr(b, 8);
1134        assert_eq!(res.array, [1, 0]); // 0x0001
1135        assert!(!overflow);
1136
1137        let (res, overflow) = OverflowingShr::overflowing_shr(b, 16);
1138        assert_eq!(res.array, [0, 1]); // wraps
1139        assert!(overflow);
1140
1141        // Test wrapping_shl
1142        let c = TestInt::from(1u8);
1143        assert_eq!(WrappingShl::wrapping_shl(c, 4).array, [16, 0]);
1144        assert_eq!(WrappingShl::wrapping_shl(c, 16).array, [1, 0]); // wraps
1145        assert_eq!(WrappingShl::wrapping_shl(c, 17).array, [2, 0]); // wraps
1146
1147        // Test wrapping_shr
1148        let d = TestInt::from(0x8000u16);
1149        assert_eq!(WrappingShr::wrapping_shr(d, 4).array, [0, 0x08]);
1150        assert_eq!(WrappingShr::wrapping_shr(d, 16).array, [0, 0x80]); // wraps
1151        assert_eq!(WrappingShr::wrapping_shr(d, 17).array, [0, 0x40]); // wraps
1152
1153        // Test checked_shl
1154        let e = TestInt::from(1u8);
1155        assert_eq!(CheckedShl::checked_shl(e, 4), Some(TestInt::from(16u8)));
1156        assert_eq!(
1157            CheckedShl::checked_shl(e, 15),
1158            Some(TestInt::from(0x8000u16))
1159        );
1160        assert_eq!(CheckedShl::checked_shl(e, 16), None); // overflow
1161
1162        // Test checked_shr
1163        let f = TestInt::from(0x8000u16);
1164        assert_eq!(CheckedShr::checked_shr(f, 15), Some(TestInt::from(1u8)));
1165        assert_eq!(CheckedShr::checked_shr(f, 16), None); // overflow
1166
1167        // Test edge case: zero shift
1168        let g = TestInt::from(42u8);
1169        assert_eq!(OverflowingShl::overflowing_shl(g, 0), (g, false));
1170        assert_eq!(OverflowingShr::overflowing_shr(g, 0), (g, false));
1171        assert_eq!(WrappingShl::wrapping_shl(g, 0), g);
1172        assert_eq!(WrappingShr::wrapping_shr(g, 0), g);
1173        assert_eq!(CheckedShl::checked_shl(g, 0), Some(g));
1174        assert_eq!(CheckedShr::checked_shr(g, 0), Some(g));
1175    }
1176
1177    #[test]
1178    fn test_const_shift_traits_n0() {
1179        // Test with N=0 (zero-sized type)
1180        type ZeroInt = FixedUInt<u8, 0>;
1181        let z = ZeroInt::from_array([]);
1182
1183        // All shifts on zero-sized type should overflow
1184        assert_eq!(OverflowingShl::overflowing_shl(z, 0), (z, true));
1185        assert_eq!(OverflowingShr::overflowing_shr(z, 0), (z, true));
1186        assert_eq!(WrappingShl::wrapping_shl(z, 0), z);
1187        assert_eq!(WrappingShr::wrapping_shr(z, 0), z);
1188        assert_eq!(CheckedShl::checked_shl(z, 0), None);
1189        assert_eq!(CheckedShr::checked_shr(z, 0), None);
1190    }
1191
1192    #[test]
1193    #[cfg(feature = "num-traits")]
1194    fn test_num_traits_shift_wrappers() {
1195        use num_traits::{CheckedShl, CheckedShr, WrappingShl, WrappingShr};
1196
1197        type TestInt = FixedUInt<u8, 2>;
1198
1199        let a = TestInt::from(1u8);
1200
1201        // num_traits::WrappingShl is by-ref (upstream signature).
1202        assert_eq!(WrappingShl::wrapping_shl(&a, 4), TestInt::from(16u8));
1203        assert_eq!(WrappingShl::wrapping_shl(&a, 16), a); // wraps
1204
1205        // num_traits::WrappingShr
1206        let b = TestInt::from(16u8);
1207        assert_eq!(WrappingShr::wrapping_shr(&b, 4), TestInt::from(1u8));
1208
1209        // num_traits::CheckedShl
1210        assert_eq!(CheckedShl::checked_shl(&a, 4), Some(TestInt::from(16u8)));
1211        assert_eq!(CheckedShl::checked_shl(&a, 16), None);
1212
1213        // num_traits::CheckedShr
1214        assert_eq!(CheckedShr::checked_shr(&b, 4), Some(TestInt::from(1u8)));
1215        assert_eq!(CheckedShr::checked_shr(&b, 16), None);
1216    }
1217
1218    #[test]
1219    fn test_unbounded_shift() {
1220        type U16 = FixedUInt<u8, 2>;
1221
1222        let one = U16::from(1u8);
1223
1224        // Normal shifts (within bounds)
1225        assert_eq!(UnboundedShl::unbounded_shl(one, 0), one);
1226        assert_eq!(UnboundedShl::unbounded_shl(one, 4), U16::from(16u8));
1227        assert_eq!(UnboundedShl::unbounded_shl(one, 15), U16::from(0x8000u16));
1228
1229        assert_eq!(UnboundedShr::unbounded_shr(U16::from(0x8000u16), 15), one);
1230        assert_eq!(UnboundedShr::unbounded_shr(U16::from(16u8), 4), one);
1231
1232        // At boundary (shift by bit width) - returns 0
1233        assert_eq!(UnboundedShl::unbounded_shl(one, 16), U16::from(0u8));
1234        assert_eq!(
1235            UnboundedShr::unbounded_shr(U16::from(0xFFFFu16), 16),
1236            U16::from(0u8)
1237        );
1238
1239        // Beyond boundary - returns 0
1240        assert_eq!(
1241            UnboundedShl::unbounded_shl(U16::from(0xFFFFu16), 17),
1242            U16::from(0u8)
1243        );
1244        assert_eq!(
1245            UnboundedShl::unbounded_shl(U16::from(0xFFFFu16), 100),
1246            U16::from(0u8)
1247        );
1248        assert_eq!(
1249            UnboundedShr::unbounded_shr(U16::from(0xFFFFu16), 17),
1250            U16::from(0u8)
1251        );
1252        assert_eq!(
1253            UnboundedShr::unbounded_shr(U16::from(0xFFFFu16), 100),
1254            U16::from(0u8)
1255        );
1256
1257        // Test with different word sizes
1258        type U32 = FixedUInt<u8, 4>;
1259        let one32 = U32::from(1u8);
1260        assert_eq!(
1261            UnboundedShl::unbounded_shl(one32, 31),
1262            U32::from(0x80000000u32)
1263        );
1264        assert_eq!(UnboundedShl::unbounded_shl(one32, 32), U32::from(0u8));
1265        assert_eq!(
1266            UnboundedShr::unbounded_shr(U32::from(0x80000000u32), 31),
1267            one32
1268        );
1269        assert_eq!(
1270            UnboundedShr::unbounded_shr(U32::from(0x80000000u32), 32),
1271            U32::from(0u8)
1272        );
1273    }
1274
1275    #[test]
1276    fn test_unbounded_shift_polymorphic() {
1277        fn test_unbounded<T>(val: T, shift: u32, expected_shl: T, expected_shr: T)
1278        where
1279            T: UnboundedShl<Output = T> + UnboundedShr<Output = T> + Eq + core::fmt::Debug + Copy,
1280        {
1281            assert_eq!(UnboundedShl::unbounded_shl(val, shift), expected_shl);
1282            assert_eq!(UnboundedShr::unbounded_shr(val, shift), expected_shr);
1283        }
1284
1285        // Test with FixedUInt layouts
1286        type U8x2 = FixedUInt<u8, 2>;
1287        type U8x4 = FixedUInt<u8, 4>;
1288        type U16x2 = FixedUInt<u16, 2>;
1289
1290        // Same logical shift, different layouts
1291        test_unbounded(U8x2::from(1u8), 4, U8x2::from(16u8), U8x2::from(0u8));
1292        test_unbounded(U8x4::from(1u8), 4, U8x4::from(16u8), U8x4::from(0u8));
1293        test_unbounded(U16x2::from(1u8), 4, U16x2::from(16u8), U16x2::from(0u8));
1294
1295        // Test with primitives
1296        test_unbounded(1u8, 4, 16u8, 0u8);
1297        test_unbounded(1u16, 4, 16u16, 0u16);
1298        test_unbounded(1u32, 4, 16u32, 0u32);
1299
1300        // Boundary tests
1301        test_unbounded(1u8, 8, 0u8, 0u8);
1302        test_unbounded(U8x2::from(1u8), 16, U8x2::from(0u8), U8x2::from(0u8));
1303    }
1304
1305    #[test]
1306    fn test_bit_width() {
1307        use const_num_traits::BitWidth;
1308        type U16 = FixedUInt<u8, 2>;
1309        assert_eq!(BitWidth::bit_width(U16::from(0u8)), 0);
1310        assert_eq!(BitWidth::bit_width(U16::from(1u8)), 1);
1311        assert_eq!(BitWidth::bit_width(U16::from(2u8)), 2);
1312        assert_eq!(BitWidth::bit_width(U16::from(3u8)), 2);
1313        assert_eq!(BitWidth::bit_width(U16::from(255u8)), 8);
1314        assert_eq!(BitWidth::bit_width(U16::from(256u16)), 9);
1315        assert_eq!(BitWidth::bit_width(U16::from(0xFFFFu16)), 16);
1316    }
1317
1318    #[test]
1319    fn test_bits_precision() {
1320        use const_num_traits::BitsPrecision;
1321        // Fixed carrier: width == BIT_SIZE, value-independent (= capacity).
1322        type U16 = FixedUInt<u8, 2>;
1323        type U32 = FixedUInt<u32, 1>;
1324        assert_eq!(BitsPrecision::bits_precision(&U16::from(0u8)), 16);
1325        assert_eq!(BitsPrecision::bits_precision(&U16::from(0xFFFFu16)), 16);
1326        assert_eq!(BitsPrecision::bits_precision(&U32::from(0u8)), 32);
1327        assert_eq!(BitsPrecision::bits_precision(&U32::from(7u8)), 32);
1328    }
1329
1330    #[test]
1331    fn test_highest_lowest_one() {
1332        use const_num_traits::{HighestOne, LowestOne};
1333        type U16 = FixedUInt<u8, 2>;
1334        assert_eq!(HighestOne::highest_one(U16::from(0u8)), None);
1335        assert_eq!(HighestOne::highest_one(U16::from(1u8)), Some(0));
1336        assert_eq!(HighestOne::highest_one(U16::from(0b1010_0000u8)), Some(7));
1337        assert_eq!(HighestOne::highest_one(U16::from(0x8000u16)), Some(15));
1338
1339        assert_eq!(LowestOne::lowest_one(U16::from(0u8)), None);
1340        assert_eq!(LowestOne::lowest_one(U16::from(1u8)), Some(0));
1341        assert_eq!(LowestOne::lowest_one(U16::from(0b0010_1000u8)), Some(3));
1342        assert_eq!(LowestOne::lowest_one(U16::from(0x8000u16)), Some(15));
1343    }
1344
1345    #[test]
1346    fn test_isolate_highest_lowest_one() {
1347        use const_num_traits::{IsolateHighestOne, IsolateLowestOne};
1348        type U16 = FixedUInt<u8, 2>;
1349        // zero → zero
1350        assert_eq!(
1351            IsolateHighestOne::isolate_highest_one(U16::from(0u8)),
1352            U16::from(0u8)
1353        );
1354        assert_eq!(
1355            IsolateLowestOne::isolate_lowest_one(U16::from(0u8)),
1356            U16::from(0u8)
1357        );
1358        // nonzero
1359        assert_eq!(
1360            IsolateHighestOne::isolate_highest_one(U16::from(0b1010_0000u8)),
1361            U16::from(0b1000_0000u8)
1362        );
1363        assert_eq!(
1364            IsolateLowestOne::isolate_lowest_one(U16::from(0b1010_1000u8)),
1365            U16::from(0b0000_1000u8)
1366        );
1367        // power of two: highest == lowest == self
1368        let p: U16 = U16::from(0x0100u16);
1369        assert_eq!(IsolateHighestOne::isolate_highest_one(p), p);
1370        assert_eq!(IsolateLowestOne::isolate_lowest_one(p), p);
1371    }
1372
1373    #[test]
1374    fn test_shl_shr_exact() {
1375        use const_num_traits::{ShlExact, ShrExact};
1376        type U16 = FixedUInt<u8, 2>;
1377        // shl_exact: must not lose bits
1378        assert_eq!(
1379            ShlExact::shl_exact(U16::from(1u8), 4),
1380            Some(U16::from(16u8))
1381        );
1382        assert_eq!(ShlExact::shl_exact(U16::from(0u8), 8), Some(U16::from(0u8)));
1383        // dropping a high bit → None
1384        assert_eq!(ShlExact::shl_exact(U16::from(0x8000u16), 1), None);
1385        // rhs >= BIT_SIZE → None
1386        assert_eq!(ShlExact::shl_exact(U16::from(1u8), 16), None);
1387
1388        // shr_exact: must not lose set bits
1389        assert_eq!(
1390            ShrExact::shr_exact(U16::from(16u8), 4),
1391            Some(U16::from(1u8))
1392        );
1393        assert_eq!(ShrExact::shr_exact(U16::from(0u8), 8), Some(U16::from(0u8)));
1394        // dropping a low bit → None
1395        assert_eq!(ShrExact::shr_exact(U16::from(0b0001u8), 1), None);
1396        assert_eq!(ShrExact::shr_exact(U16::from(0b0011u8), 1), None);
1397        // rhs >= BIT_SIZE → None
1398        assert_eq!(ShrExact::shr_exact(U16::from(1u8), 16), None);
1399    }
1400
1401    #[test]
1402    #[allow(clippy::needless_borrows_for_generic_args)]
1403    fn test_ref_receivers_compile_through() {
1404        use const_num_traits::{BitWidth, IsolateHighestOne, IsolateLowestOne, ShlExact, ShrExact};
1405        type U16 = FixedUInt<u8, 2>;
1406        let v = U16::from(0b0010_1000u8);
1407        assert_eq!(BitWidth::bit_width(&v), 6);
1408        assert_eq!(
1409            IsolateHighestOne::isolate_highest_one(&v),
1410            U16::from(0b0010_0000u8)
1411        );
1412        assert_eq!(
1413            IsolateLowestOne::isolate_lowest_one(&v),
1414            U16::from(0b0000_1000u8)
1415        );
1416        assert_eq!(ShlExact::shl_exact(&v, 2), Some(U16::from(0b1010_0000u8)));
1417        assert_eq!(ShrExact::shr_exact(&v, 3), Some(U16::from(0b0000_0101u8)));
1418    }
1419
1420    #[test]
1421    fn test_funnel_shifts() {
1422        use const_num_traits::{FunnelShl, FunnelShr};
1423        type U16 = FixedUInt<u8, 2>;
1424
1425        // 0x0001_8000 << 1 = 0x0003_0000; high half = 0x0003.
1426        assert_eq!(
1427            FunnelShl::funnel_shl(U16::from(0x0001u16), U16::from(0x8000u16), 1),
1428            U16::from(0x0003u16),
1429        );
1430        // n == 0: returns self (hi)
1431        assert_eq!(
1432            FunnelShl::funnel_shl(U16::from(0xABCDu16), U16::from(0xFFFFu16), 0),
1433            U16::from(0xABCDu16),
1434        );
1435        // 0x0001_8000 >> 1 = 0x0000_C000; low half = 0xC000.
1436        assert_eq!(
1437            FunnelShr::funnel_shr(U16::from(0x0001u16), U16::from(0x8000u16), 1),
1438            U16::from(0xC000u16),
1439        );
1440        // n == 0: returns rhs (lo)
1441        assert_eq!(
1442            FunnelShr::funnel_shr(U16::from(0xABCDu16), U16::from(0x1234u16), 0),
1443            U16::from(0x1234u16),
1444        );
1445
1446        // Reference receivers
1447        let hi = U16::from(0x0001u16);
1448        let lo = U16::from(0x8000u16);
1449        assert_eq!(FunnelShl::funnel_shl(&hi, &lo, 1), U16::from(0x0003u16));
1450        assert_eq!(FunnelShr::funnel_shr(&hi, &lo, 1), U16::from(0xC000u16));
1451    }
1452
1453    #[test]
1454    #[should_panic(expected = "funnel_shl: n out of range")]
1455    fn test_funnel_shl_panics_at_bit_size() {
1456        use const_num_traits::FunnelShl;
1457        type U16 = FixedUInt<u8, 2>;
1458        let _ = FunnelShl::funnel_shl(U16::from(1u8), U16::from(0u8), 16);
1459    }
1460
1461    #[test]
1462    fn test_deposit_extract_bits() {
1463        use const_num_traits::Nct;
1464        use const_num_traits::{DepositBits, ExtractBits};
1465        type U16 = FixedUInt<u8, 2, Nct>;
1466
1467        // Mirror of the primitive doctest:
1468        // deposit_bits(0b101, mask=0b1111_0000) = 0b0101_0000
1469        assert_eq!(
1470            DepositBits::deposit_bits(U16::from(0b101u8), U16::from(0b1111_0000u8)),
1471            U16::from(0b0101_0000u8),
1472        );
1473        // extract_bits(0b0101_0011, mask=0b1111_0000) = 0b101
1474        assert_eq!(
1475            ExtractBits::extract_bits(U16::from(0b0101_0011u8), U16::from(0b1111_0000u8)),
1476            U16::from(0b101u8),
1477        );
1478
1479        // Empty mask → 0 (both directions).
1480        assert_eq!(
1481            DepositBits::deposit_bits(U16::from(0xFFFFu16), U16::from(0u8)),
1482            U16::from(0u8),
1483        );
1484        assert_eq!(
1485            ExtractBits::extract_bits(U16::from(0xFFFFu16), U16::from(0u8)),
1486            U16::from(0u8),
1487        );
1488
1489        // All-ones mask → identity.
1490        assert_eq!(
1491            DepositBits::deposit_bits(U16::from(0xABCDu16), U16::from(0xFFFFu16)),
1492            U16::from(0xABCDu16),
1493        );
1494        assert_eq!(
1495            ExtractBits::extract_bits(U16::from(0xABCDu16), U16::from(0xFFFFu16)),
1496            U16::from(0xABCDu16),
1497        );
1498
1499        // Round-trip on a non-trivial mask. extract then deposit through the
1500        // same mask gives back the originally-selected bits in their original
1501        // positions.
1502        let mask = U16::from(0b1010_1010u8);
1503        let v = U16::from(0b1111_1111u8);
1504        let extracted = ExtractBits::extract_bits(v, mask);
1505        let redeposited = DepositBits::deposit_bits(extracted, mask);
1506        assert_eq!(redeposited, v & mask);
1507
1508        // Reference receivers
1509        let v_ref = U16::from(0b0110_0110u8);
1510        let m_ref = U16::from(0b1111_0000u8);
1511        assert_eq!(
1512            ExtractBits::extract_bits(&v_ref, &m_ref),
1513            U16::from(0b0110u8),
1514        );
1515        assert_eq!(
1516            DepositBits::deposit_bits(&U16::from(0b101u8), &m_ref),
1517            U16::from(0b0101_0000u8),
1518        );
1519    }
1520
1521    // --- Empirical const-evaluability proofs ---------------------------------
1522    //
1523    // Each trait method below is invoked from a `c0nst fn` wrapper, so the
1524    // surrounding `c0nst::c0nst!` block forces the compiler to treat it as
1525    // const-callable when the `nightly` feature is enabled. The
1526    // `nightly_const_eval_*` tests then bind the wrapper's result to a
1527    // `const` item — proving the trait method actually evaluates at compile
1528    // time, not just that the impl is annotated `c0nst`.
1529
1530    c0nst::c0nst! {
1531        pub c0nst fn const_overflowing_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> (FixedUInt<T, N, P>, bool) {
1532            OverflowingShl::overflowing_shl(v, bits)
1533        }
1534        pub c0nst fn const_overflowing_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> (FixedUInt<T, N, P>, bool) {
1535            OverflowingShr::overflowing_shr(v, bits)
1536        }
1537        pub c0nst fn const_wrapping_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> FixedUInt<T, N, P> {
1538            WrappingShl::wrapping_shl(v, bits)
1539        }
1540        pub c0nst fn const_wrapping_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> FixedUInt<T, N, P> {
1541            WrappingShr::wrapping_shr(v, bits)
1542        }
1543        pub c0nst fn const_checked_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> Option<FixedUInt<T, N, P>> {
1544            CheckedShl::checked_shl(v, bits)
1545        }
1546        pub c0nst fn const_checked_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> Option<FixedUInt<T, N, P>> {
1547            CheckedShr::checked_shr(v, bits)
1548        }
1549        pub c0nst fn const_unbounded_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> FixedUInt<T, N, P> {
1550            UnboundedShl::unbounded_shl(v, bits)
1551        }
1552        pub c0nst fn const_unbounded_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> FixedUInt<T, N, P> {
1553            UnboundedShr::unbounded_shr(v, bits)
1554        }
1555        pub c0nst fn const_highest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> Option<u32> {
1556            const_num_traits::HighestOne::highest_one(v)
1557        }
1558        pub c0nst fn const_lowest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> Option<u32> {
1559            const_num_traits::LowestOne::lowest_one(v)
1560        }
1561        pub c0nst fn const_bit_width<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> u32 {
1562            const_num_traits::BitWidth::bit_width(v)
1563        }
1564        pub c0nst fn const_isolate_highest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> FixedUInt<T, N, P> {
1565            const_num_traits::IsolateHighestOne::isolate_highest_one(v)
1566        }
1567        pub c0nst fn const_isolate_lowest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> FixedUInt<T, N, P> {
1568            const_num_traits::IsolateLowestOne::isolate_lowest_one(v)
1569        }
1570        pub c0nst fn const_shl_exact<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> Option<FixedUInt<T, N, P>> {
1571            const_num_traits::ShlExact::shl_exact(v, bits)
1572        }
1573        pub c0nst fn const_shr_exact<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> Option<FixedUInt<T, N, P>> {
1574            const_num_traits::ShrExact::shr_exact(v, bits)
1575        }
1576        pub c0nst fn const_funnel_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(hi: FixedUInt<T, N, P>, lo: FixedUInt<T, N, P>, n: u32) -> FixedUInt<T, N, P> {
1577            const_num_traits::FunnelShl::funnel_shl(hi, lo, n)
1578        }
1579        pub c0nst fn const_funnel_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(hi: FixedUInt<T, N, P>, lo: FixedUInt<T, N, P>, n: u32) -> FixedUInt<T, N, P> {
1580            const_num_traits::FunnelShr::funnel_shr(hi, lo, n)
1581        }
1582        pub c0nst fn const_deposit_bits<T: [c0nst] ConstMachineWord + MachineWord, const N: usize>(v: FixedUInt<T, N, Nct>, mask: FixedUInt<T, N, Nct>) -> FixedUInt<T, N, Nct> {
1583            const_num_traits::DepositBits::deposit_bits(v, mask)
1584        }
1585        pub c0nst fn const_extract_bits<T: [c0nst] ConstMachineWord + MachineWord, const N: usize>(v: FixedUInt<T, N, Nct>, mask: FixedUInt<T, N, Nct>) -> FixedUInt<T, N, Nct> {
1586            const_num_traits::ExtractBits::extract_bits(v, mask)
1587        }
1588    }
1589
1590    #[test]
1591    fn nightly_const_eval_bit_traits() {
1592        type U16 = FixedUInt<u8, 2>;
1593
1594        // Runtime smoke — the wrappers themselves work.
1595        let v = U16::from(1u8);
1596        assert_eq!(const_overflowing_shl(v, 4), (U16::from(16u8), false));
1597        assert_eq!(const_wrapping_shl(v, 4), U16::from(16u8));
1598        assert_eq!(const_checked_shl(v, 16), None);
1599        assert_eq!(const_bit_width(U16::from(0xFFu8)), 8);
1600
1601        // The real proof — evaluate at compile time.
1602        #[cfg(feature = "nightly")]
1603        {
1604            const V: U16 = FixedUInt::from_array([1, 0]);
1605            const V_FF: U16 = FixedUInt::from_array([0xFF, 0]);
1606            const V_MASK: U16 = FixedUInt::from_array([0b1010_1000, 0]);
1607            const HI: U16 = FixedUInt::from_array([1, 0]);
1608            const LO: U16 = FixedUInt::from_array([0, 0x80]);
1609
1610            const OSHL: (U16, bool) = const_overflowing_shl(V, 4);
1611            const OSHR: (U16, bool) = const_overflowing_shr(V_FF, 4);
1612            const WSHL: U16 = const_wrapping_shl(V, 4);
1613            const WSHR: U16 = const_wrapping_shr(V_FF, 4);
1614            const CSHL: Option<U16> = const_checked_shl(V, 16);
1615            const CSHR: Option<U16> = const_checked_shr(V, 4);
1616            const USHL: U16 = const_unbounded_shl(V, 8);
1617            const USHR: U16 = const_unbounded_shr(V_FF, 4);
1618            const HI_ONE: Option<u32> = const_highest_one(V_FF);
1619            const LO_ONE: Option<u32> = const_lowest_one(V_MASK);
1620            const BW: u32 = const_bit_width(V_FF);
1621            const IH: U16 = const_isolate_highest_one(V_MASK);
1622            const IL: U16 = const_isolate_lowest_one(V_MASK);
1623            const SHLEX: Option<U16> = const_shl_exact(V, 4);
1624            const SHREX: Option<U16> = const_shr_exact(FixedUInt::from_array([16, 0]), 4);
1625            const FSHL: U16 = const_funnel_shl(HI, LO, 1);
1626            const FSHR: U16 = const_funnel_shr(HI, LO, 1);
1627            const DEP: U16 = const_deposit_bits(
1628                FixedUInt::from_array([0b101, 0]),
1629                FixedUInt::from_array([0b1111_0000, 0]),
1630            );
1631            const EXT: U16 = const_extract_bits(
1632                FixedUInt::from_array([0b0101_0011, 0]),
1633                FixedUInt::from_array([0b1111_0000, 0]),
1634            );
1635
1636            // Sanity-check a representative subset of the const results.
1637            assert_eq!(OSHL.0.array, [16, 0]);
1638            assert!(!OSHL.1);
1639            assert_eq!(OSHR.0.array, [0x0F, 0]);
1640            assert!(!OSHR.1);
1641            assert_eq!(WSHL.array, [16, 0]);
1642            assert_eq!(WSHR.array, [0x0F, 0]);
1643            assert!(CSHL.is_none());
1644            assert!(CSHR.is_some());
1645            assert_eq!(USHL.array, [0, 1]);
1646            assert_eq!(USHR.array, [0x0F, 0]);
1647            assert_eq!(HI_ONE, Some(7));
1648            assert_eq!(LO_ONE, Some(3));
1649            assert_eq!(BW, 8);
1650            assert_eq!(IH.array, [0b1000_0000, 0]);
1651            assert_eq!(IL.array, [0b0000_1000, 0]);
1652            assert!(SHLEX.is_some());
1653            assert!(SHREX.is_some());
1654            assert_eq!(FSHL.array, [0x03, 0]);
1655            assert_eq!(FSHR.array, [0x00, 0xC0]);
1656            assert_eq!(DEP.array, [0b0101_0000, 0]);
1657            assert_eq!(EXT.array, [0b101, 0]);
1658        }
1659    }
1660}