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