Skip to main content

cubecl_common/
obfuscation.rs

1//! Internal home of the [`obfuscate!`](crate::obfuscate) macro. The
2//! module itself is private — the macro is exported at the crate root,
3//! which is where its documentation lives.
4
5/// Generate a type-erased, inline wrapper for a single value.
6///
7/// Expands into a private module containing an `Opaque` struct that
8/// stores one value of `type:` behind a fixed-shape representation that
9/// never names the inner type. Downstream crates that handle the wrapper
10/// never have to resolve the inner type, which breaks the transitive
11/// monomorphization chain and can visibly cut compile times for deeply
12/// generic types.
13///
14/// # Example
15///
16/// ```rust
17/// use cubecl_common::obfuscate;
18///
19/// #[derive(Clone, Debug, PartialEq)]
20/// struct Tensor { shape: Vec<usize>, data: Vec<f32> }
21///
22/// obfuscate!(
23///     type: Tensor,
24///     module: tensor,
25///     derives: [Send, Sync, Debug, PartialEq, Clone],
26///     align: 8,
27/// );
28///
29/// # fn main() {
30/// let t = tensor::Opaque::new(Tensor { shape: vec![2, 2], data: vec![1.0; 4] });
31/// let copy = t.clone();
32/// assert_eq!(t, copy);
33///
34/// // Recover the concrete type when you actually need its fields.
35/// let inner: Tensor = t.into_inner();
36/// assert_eq!(inner.shape, vec![2, 2]);
37/// # }
38/// ```
39///
40/// The macro call must sit at module scope (or crate root); it cannot be
41/// nested inside a function body, since the generated module's `super`
42/// would not see locals.
43///
44/// # When to reach for this
45///
46/// - A type appears in many generic instantiations and shows up as a
47///   compile-time bottleneck.
48/// - You can't use `Box<dyn Trait>`: you need the concrete type back, or
49///   you want to avoid the heap.
50/// - The wrapper's API surface (`new` / `as_ref` / `as_mut` / `into_inner`)
51///   is enough for the boundary you're crossing.
52///
53/// # Syntax
54///
55/// ```text
56/// obfuscate!(
57///     type:    <inner-type>,
58///     module:  <module-name>,
59///     derives: [<trait>, ...],      // optional
60///     align:   <integer-literal>,   // optional; default is 64
61/// );
62/// ```
63///
64/// `type:` and `module:` are mandatory and must appear in that order.
65/// `derives:` and `align:` are both optional; if present, they must
66/// appear in the order shown above.
67///
68/// `derives:` controls which opt-in trait impls are emitted on
69/// `Opaque`. Omit the key entirely (or pass `[]`) for none.
70///
71/// `align:` has two modes:
72///
73/// - **Omitted (default).** The wrapper is fixed at 64-byte alignment.
74///   The macro only verifies `align_of::<inner>() <= 64`; over-aligned
75///   small types are wasted bytes but it always works.
76/// - **Specified.** The wrapper has exactly that alignment, and the
77///   macro asserts the literal equals
78///   `max(core::mem::align_of::<inner>(), core::mem::align_of::<*mut ()>())`.
79///   Wrong values are a `const` assertion failure at the call site.
80///
81/// A literal is required because `#[repr(align(N))]` does not accept
82/// const expressions — the macro can't substitute `align_of::<inner>()`
83/// for you.
84///
85/// # Generated API
86///
87/// The macro emits a single struct, `Opaque`, with inherent methods
88/// scoped `pub(super)` — visible to the parent module of the generated
89/// wrapper and nowhere else:
90///
91/// ```ignore
92/// impl Opaque {
93///     fn new(inner: Inner) -> Self;
94///     fn as_ref(&self) -> &Inner;
95///     fn as_mut(&mut self) -> &mut Inner;
96///     fn into_inner(self) -> Inner;
97/// }
98/// // Plus a `Drop` impl that runs the inner's destructor exactly once.
99/// // `into_inner` suppresses this drop and returns the value instead.
100/// ```
101///
102/// Any extra trait impls listed in `derives:` are emitted alongside.
103///
104/// # Derives
105///
106/// The wrapper is `!Send + !Sync` and has no extra trait impls by
107/// default. Opt in via the `derives:` list:
108///
109/// | Token | Kind | Behaviour |
110/// |---|---|---|
111/// | `Send`, `Sync` | `unsafe impl` | Caller asserts the inner type satisfies the marker. No bound check is emitted. |
112/// | `Debug`, `Display` | safe | Forwards the `Formatter` to the inner value — width/precision/alternate flags survive. |
113/// | `PartialEq` | safe | `self.as_ref() == other.as_ref()` |
114/// | `Eq` | safe marker | Pairs with `PartialEq`. |
115/// | `Hash` | safe | Hashes the inner value. |
116/// | `Clone` | safe | Deep clone via the inner's `Clone`. Storage is independent. |
117/// | `Default` | safe | Constructs from `<Inner as Default>::default()`. |
118///
119/// Any other identifier in `derives: [...]` is a compile error at the
120/// call site. All non-marker impls require the inner type to implement
121/// the trait — the impl is concrete (not generic), so the bound is
122/// checked at expansion.
123///
124/// # Storage and safety
125///
126/// The wrapper holds the inner value inline — no allocation. Storage
127/// uses pointer-sized [`MaybeUninit<*mut ()>`](core::mem::MaybeUninit)
128/// slots rather than bytes, so any pointers owned by the inner value
129/// retain their provenance through the round trip. A naive
130/// `[u8; size_of::<T>()]` buffer would strip pointer provenance on
131/// read, breaking under Miri / Tree Borrows for any inner type that
132/// owns an `Arc`, `Box`, or similar.
133///
134/// The wrapper carries `#[repr(C, align(N))]`. `N` is either the
135/// caller-declared `align:` literal (asserted strictly against the
136/// required value) or `64` by default (asserted permissively against
137/// `align_of::<inner>() <= 64`). Wrong values are a compile error
138/// rather than runtime UB.
139///
140/// # Why a macro?
141///
142/// `size_of::<T>()` and `align_of::<T>()` are usable in `const` position
143/// only for concrete types. Without the unstable
144/// `feature(generic_const_exprs)`, you cannot write
145/// `struct Wrapper<T> { bytes: [u8; size_of::<T>()] }` as a generic. The
146/// macro side-steps this by expanding a fresh, concrete wrapper per use
147/// site.
148#[macro_export]
149macro_rules! obfuscate {
150    // ──────────────────────────────────────────────────────────────────
151    // Public arms. Each one normalises missing optional fields and
152    // delegates to the internal `@impl` arm below. `check:` is a
153    // sentinel that picks between the strict and relaxed assertion.
154    // ──────────────────────────────────────────────────────────────────
155
156    // Full: caller specifies both `derives:` and `align:`.
157    (
158        type: $inner:ty,
159        module: $mod_name:ident,
160        derives: [ $($trait:ident),* $(,)? ],
161        align: $align:literal $(,)?
162    ) => {
163        $crate::obfuscate!(
164            @impl
165            type: $inner,
166            module: $mod_name,
167            derives: [$($trait),*],
168            align: $align,
169            check: strict,
170        );
171    };
172
173    // `derives:` only — default 64-byte alignment with the relaxed check.
174    (
175        type: $inner:ty,
176        module: $mod_name:ident,
177        derives: [ $($trait:ident),* $(,)? ] $(,)?
178    ) => {
179        $crate::obfuscate!(
180            @impl
181            type: $inner,
182            module: $mod_name,
183            derives: [$($trait),*],
184            align: 64,
185            check: relaxed,
186        );
187    };
188
189    // `align:` only — empty derives, exact alignment.
190    (
191        type: $inner:ty,
192        module: $mod_name:ident,
193        align: $align:literal $(,)?
194    ) => {
195        $crate::obfuscate!(
196            @impl
197            type: $inner,
198            module: $mod_name,
199            derives: [],
200            align: $align,
201            check: strict,
202        );
203    };
204
205    // Minimal — no derives, default 64-byte alignment.
206    (
207        type: $inner:ty,
208        module: $mod_name:ident $(,)?
209    ) => {
210        $crate::obfuscate!(
211            @impl
212            type: $inner,
213            module: $mod_name,
214            derives: [],
215            align: 64,
216            check: relaxed,
217        );
218    };
219
220    // ──────────────────────────────────────────────────────────────────
221    // Internal body arm. Single source of truth for the generated
222    // module's layout, methods, and trait impls.
223    // ──────────────────────────────────────────────────────────────────
224    (@impl
225        type: $inner:ty,
226        module: $mod_name:ident,
227        derives: [ $($trait:ident),* ],
228        align: $align:literal,
229        check: $check:ident $(,)?
230    ) => {
231        mod $mod_name {
232            #[allow(unused_imports)]
233            use super::*;
234
235            const SIZE: usize = ::core::mem::size_of::<$inner>();
236            const SLOT: usize = ::core::mem::size_of::<*mut ()>();
237
238            /// Number of pointer-sized slots needed to cover `$inner`.
239            /// Round up; the extra bytes past `SIZE` are never read as
240            /// part of the inner value.
241            const SLOTS: usize = SIZE.div_ceil(SLOT);
242
243            // Alignment assertion. Routed through a helper so the body
244            // doesn't branch on `$check` — declarative macros can't
245            // pattern-match an ident inside an expression position.
246            $crate::__obfuscate_check_align!($check, $inner, $align);
247
248            #[doc = concat!(
249                "Inline, opaque storage for one `",
250                stringify!($inner),
251                "` value. **Alignment:** ",
252                stringify!($align),
253                " bytes."
254            )]
255            ///
256            /// `#[repr(C, align(N))]` raises the struct's alignment to
257            /// the literal above. The const assertion in the macro
258            /// expansion guarantees the literal is sufficient for
259            /// `$inner`'s alignment, so the `*const _ as *const $inner`
260            /// cast in the methods is sound without naming `$inner` in
261            /// the struct definition.
262            ///
263            /// Slots are typed as pointer-sized `MaybeUninit<*mut ()>`
264            /// rather than `u8`: the `*mut ()` payload type lets
265            /// pointers owned by the inner value retain their
266            /// provenance through `ptr::write` → `ptr::read`, while
267            /// `MaybeUninit` allows the slots to legally hold
268            /// uninitialised padding bytes (which arise for enum types
269            /// whose variants don't fill the whole discriminant).
270            /// Neither wrapper names `$inner`, so the type-erasure
271            /// goal is preserved.
272            #[repr(C, align($align))]
273            pub(super) struct Opaque {
274                data: [::core::mem::MaybeUninit<*mut ()>; SLOTS],
275            }
276
277            // Impl blocks live in a sub-module to keep the layout
278            // declaration (above) visually separate from the
279            // implementation. `mod impls` is a descendant of
280            // `$mod_name`, so it can see the private `data` field; the
281            // `use super::super::*` brings the caller's scope (which
282            // is where `$inner` is defined) into reach.
283            mod impls {
284                #[allow(unused_imports)]
285                use super::super::*;
286                use super::{Opaque, SLOTS};
287
288                // Trait impls listed via `derives:`. Each token is
289                // dispatched to the helper macro, which emits exactly
290                // that one impl. Unlisted traits are not implemented —
291                // `*mut ()` makes `Opaque` `!Send + !Sync` by default.
292                $(
293                    $crate::obfuscate_impl!($trait);
294                )*
295
296                // The macro exposes a uniform API
297                // (`new`/`as_ref`/`as_mut`/`into_inner`) but not every
298                // use site needs every entry point. Silence the
299                // resulting `dead_code` warnings here rather than at
300                // every call site.
301                #[allow(dead_code)]
302                impl Opaque {
303                    /// Wrap an `$inner` value in a fresh `Opaque`.
304                    pub(in super::super) fn new(inner: $inner) -> Self {
305                        let mut wrapper = Self {
306                            data: [::core::mem::MaybeUninit::uninit(); SLOTS],
307                        };
308                        // SAFETY: `data` is at offset 0 of `Self`, and
309                        // `Self`'s alignment is `$align`, which the
310                        // const assertion above verifies is sufficient
311                        // for `$inner`'s alignment. The cast to
312                        // `*mut $inner` is therefore properly aligned.
313                        // The write covers exactly `SIZE` bytes, which
314                        // is `<= SLOTS * SLOT` bytes of valid,
315                        // exclusive storage.
316                        unsafe {
317                            (wrapper.data.as_mut_ptr() as *mut $inner).write(inner);
318                        }
319                        wrapper
320                    }
321
322                    /// Borrow the wrapped value.
323                    pub(in super::super) fn as_ref(&self) -> &$inner {
324                        // SAFETY: alignment is guaranteed by
325                        // `repr(align($align))` combined with the const
326                        // assertion that `$align` is at least as large
327                        // as `align_of::<$inner>()`. The bytes were
328                        // initialized in `new` and stay initialized
329                        // until `Drop`/`into_inner` consume them.
330                        unsafe { &*(self.data.as_ptr() as *const $inner) }
331                    }
332
333                    /// Mutably borrow the wrapped value.
334                    pub(in super::super) fn as_mut(&mut self) -> &mut $inner {
335                        // SAFETY: same as `as_ref`; `&mut self` gives
336                        // exclusive access.
337                        unsafe { &mut *(self.data.as_mut_ptr() as *mut $inner) }
338                    }
339
340                    /// Take ownership of the wrapped value, suppressing
341                    /// `Opaque`'s `Drop` so the inner value's
342                    /// destructor runs exactly once (when the returned
343                    /// owner is dropped).
344                    pub(in super::super) fn into_inner(self) -> $inner {
345                        // Wrap in `ManuallyDrop` first so our `Drop` is
346                        // already suppressed before we read the value
347                        // out — panic-safe if the read ever unwinds,
348                        // and the more idiomatic alternative to
349                        // `mem::forget` after the read.
350                        let this = ::core::mem::ManuallyDrop::new(self);
351                        // SAFETY: read the bytes through the
352                        // correctly-typed pointer. `this`'s `Drop` is
353                        // suppressed by `ManuallyDrop`, so the inner
354                        // value is not dropped twice.
355                        unsafe { ::core::ptr::read(this.data.as_ptr() as *const $inner) }
356                    }
357                }
358
359                impl ::core::ops::Drop for Opaque {
360                    fn drop(&mut self) {
361                        // SAFETY: see `as_ref`; running once per
362                        // `Opaque` since `into_inner` forgets `self`
363                        // when it consumes the value.
364                        unsafe {
365                            ::core::ptr::drop_in_place(
366                                self.data.as_mut_ptr() as *mut $inner,
367                            );
368                        }
369                    }
370                }
371            }
372        }
373    };
374}
375
376/// Internal helper. Emits the compile-time alignment assertion for
377/// [`obfuscate!`]. Not part of the public API.
378///
379/// - `strict, T, N`: asserts `N == max(align_of::<T>(), align_of::<*mut ()>())`.
380///   Used when the caller passes an explicit `align:` literal.
381/// - `relaxed, T, N`: asserts `align_of::<T>() <= N`. Used with the
382///   64-byte default so any inner type under that cap works without
383///   the caller specifying an alignment.
384#[macro_export]
385#[doc(hidden)]
386macro_rules! __obfuscate_check_align {
387    (strict, $inner:ty, $align:literal) => {
388        const _: () = {
389            let inner = ::core::mem::align_of::<$inner>();
390            let slot = ::core::mem::align_of::<*mut ()>();
391            let needed = if inner > slot { inner } else { slot };
392            assert!(
393                $align == needed,
394                "obfuscate!: the `align:` literal does not match the required value. \
395                 Set it to `max(core::mem::align_of::<YourType>(), core::mem::align_of::<*mut ()>())` \
396                 (typically 8 on 64-bit targets, larger if your type has a higher alignment). \
397                 A literal is required because `#[repr(align(N))]` does not accept \
398                 const expressions like `align_of::<T>()` — the macro can't compute it for you.",
399            );
400        };
401    };
402    (relaxed, $inner:ty, $align:literal) => {
403        const _: () = assert!(
404            ::core::mem::align_of::<$inner>() <= $align,
405            "obfuscate!: inner type's alignment exceeds the default wrapper alignment (64 bytes). \
406             Pass an explicit `align: N` literal (a power of two equal to \
407             `max(core::mem::align_of::<YourType>(), core::mem::align_of::<*mut ()>())`) \
408             to override the default.",
409        );
410    };
411}
412
413/// Internal helper. Dispatches a single token from [`obfuscate!`]'s
414/// `derives: [...]` list to the corresponding impl on the surrounding
415/// module's `Opaque` type. Not part of the public API — call
416/// [`obfuscate!`] instead.
417///
418/// Unrecognised tokens produce a "no rules expected" macro error at the
419/// call site, which is the intended behaviour for typos (`Sned`) and for
420/// derives this crate does not support.
421#[macro_export]
422#[doc(hidden)]
423macro_rules! obfuscate_impl {
424    (Send) => {
425        // SAFETY: caller of `obfuscate!` asserts that the inner type is
426        // safe to move across thread boundaries.
427        unsafe impl ::core::marker::Send for Opaque {}
428    };
429    (Sync) => {
430        // SAFETY: caller of `obfuscate!` asserts that the inner type is
431        // safe to share across thread boundaries.
432        unsafe impl ::core::marker::Sync for Opaque {}
433    };
434    (Debug) => {
435        impl ::core::fmt::Debug for Opaque {
436            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
437                ::core::fmt::Debug::fmt(self.as_ref(), f)
438            }
439        }
440    };
441    (Display) => {
442        impl ::core::fmt::Display for Opaque {
443            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
444                ::core::fmt::Display::fmt(self.as_ref(), f)
445            }
446        }
447    };
448    (PartialEq) => {
449        impl ::core::cmp::PartialEq for Opaque {
450            fn eq(&self, other: &Self) -> bool {
451                self.as_ref() == other.as_ref()
452            }
453        }
454    };
455    (Eq) => {
456        impl ::core::cmp::Eq for Opaque {}
457    };
458    (Hash) => {
459        impl ::core::hash::Hash for Opaque {
460            fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
461                ::core::hash::Hash::hash(self.as_ref(), state)
462            }
463        }
464    };
465    (Clone) => {
466        impl ::core::clone::Clone for Opaque {
467            fn clone(&self) -> Self {
468                Self::new(::core::clone::Clone::clone(self.as_ref()))
469            }
470        }
471    };
472    (Default) => {
473        impl ::core::default::Default for Opaque {
474            fn default() -> Self {
475                Self::new(::core::default::Default::default())
476            }
477        }
478    };
479}
480
481#[cfg(test)]
482mod tests {
483    extern crate alloc;
484    use alloc::string::String;
485    use alloc::sync::Arc;
486    use alloc::vec::Vec;
487    use core::sync::atomic::{AtomicUsize, Ordering};
488
489    // ------------------------------------------------------------------
490    // 1. Small plain-old-data round-trip.
491    // ------------------------------------------------------------------
492
493    #[derive(Clone, Debug, PartialEq, Eq)]
494    struct Pod {
495        a: u64,
496        b: u32,
497    }
498
499    // Align-only arm — no derives.
500    obfuscate!(
501        type: Pod,
502        module: pod,
503        align: 8,
504    );
505
506    #[test]
507    fn pod_round_trip() {
508        let value = Pod {
509            a: 0xDEAD_BEEF,
510            b: 7,
511        };
512        let wrapper = pod::Opaque::new(value.clone());
513        assert_eq!(wrapper.as_ref(), &value);
514        assert_eq!(wrapper.into_inner(), value);
515    }
516
517    #[test]
518    fn pod_mutation() {
519        let mut wrapper = pod::Opaque::new(Pod { a: 0, b: 0 });
520        wrapper.as_mut().a = 99;
521        wrapper.as_mut().b = 1;
522        assert_eq!(wrapper.as_ref(), &Pod { a: 99, b: 1 });
523    }
524
525    // ------------------------------------------------------------------
526    // Default-arm coverage: omitting `align:` should produce a wrapper
527    // with the 64-byte default and the permissive `<=` check.
528    // ------------------------------------------------------------------
529
530    obfuscate!(
531        type: Pod,
532        module: pod_default,
533        derives: [],
534    );
535
536    #[test]
537    fn opaque_default_arm_uses_64_byte_alignment() {
538        assert_eq!(core::mem::align_of::<pod_default::Opaque>(), 64);
539    }
540
541    #[test]
542    fn opaque_default_arm_round_trip_works() {
543        let value = Pod {
544            a: 0xCAFEBABE,
545            b: 3,
546        };
547        let wrapper = pod_default::Opaque::new(value.clone());
548        assert_eq!(wrapper.as_ref(), &value);
549        assert_eq!(wrapper.into_inner(), value);
550    }
551
552    #[test]
553    fn opaque_alignment_matches_declared() {
554        // The wrapper's alignment is what the caller declared via
555        // `align:`, which the macro's const assert verifies matches
556        // `max(align_of::<inner>(), align_of::<*mut ()>())`.
557        assert_eq!(core::mem::align_of::<pod::Opaque>(), 8);
558        assert_eq!(core::mem::align_of::<high_align::Opaque>(), 32);
559        assert_eq!(
560            core::mem::align_of::<zst::Opaque>(),
561            core::mem::align_of::<*mut ()>()
562        );
563    }
564
565    // ------------------------------------------------------------------
566    // 2. Heap-owning type — verifies Drop runs exactly once via `Drop`
567    //    AND that `into_inner` does NOT double-drop.
568    // ------------------------------------------------------------------
569
570    struct DropCounter(Arc<AtomicUsize>);
571    impl Drop for DropCounter {
572        fn drop(&mut self) {
573            self.0.fetch_add(1, Ordering::SeqCst);
574        }
575    }
576
577    // Minimal arm — no derives, no align (default 64).
578    obfuscate!(
579        type: DropCounter,
580        module: counted,
581    );
582
583    #[test]
584    fn drop_runs_exactly_once() {
585        let counter = Arc::new(AtomicUsize::new(0));
586        {
587            let _w = counted::Opaque::new(DropCounter(counter.clone()));
588        }
589        assert_eq!(counter.load(Ordering::SeqCst), 1);
590    }
591
592    #[test]
593    fn into_inner_does_not_double_drop() {
594        let counter = Arc::new(AtomicUsize::new(0));
595        let wrapper = counted::Opaque::new(DropCounter(counter.clone()));
596        let inner = wrapper.into_inner();
597        // The wrapper is gone but the value lives on in `inner`.
598        assert_eq!(counter.load(Ordering::SeqCst), 0);
599        drop(inner);
600        assert_eq!(counter.load(Ordering::SeqCst), 1);
601    }
602
603    // ------------------------------------------------------------------
604    // 3. High-alignment inner type — verifies the static assert is
605    //    permissive enough for what we actually use it for.
606    // ------------------------------------------------------------------
607
608    #[repr(align(32))]
609    #[derive(Clone, Debug, PartialEq, Eq)]
610    struct HighAlign {
611        data: [u64; 4],
612    }
613
614    // Align-only arm with a higher alignment.
615    obfuscate!(
616        type: HighAlign,
617        module: high_align,
618        align: 32,
619    );
620
621    #[test]
622    fn high_alignment_inner_works() {
623        let value = HighAlign { data: [1, 2, 3, 4] };
624        let wrapper = high_align::Opaque::new(value.clone());
625        // The wrapper must be exactly the declared alignment; the
626        // wrapped reference inherits it.
627        assert_eq!(core::mem::align_of::<high_align::Opaque>(), 32);
628        let r: &HighAlign = wrapper.as_ref();
629        assert_eq!((r as *const HighAlign as usize) % 32, 0);
630        assert_eq!(r, &value);
631    }
632
633    // ------------------------------------------------------------------
634    // 4. Enum with a heap allocation — exercises pointer provenance
635    //    preservation through `ptr::write` / `ptr::read`.
636    // ------------------------------------------------------------------
637
638    #[derive(Clone, Debug, PartialEq)]
639    #[allow(dead_code)]
640    enum Shape {
641        Scalar(u64),
642        Vector(Vec<u32>),
643        Nested(alloc::boxed::Box<Shape>),
644    }
645
646    // Minimal arm.
647    obfuscate!(
648        type: Shape,
649        module: shape,
650    );
651
652    #[test]
653    fn enum_with_heap_round_trip() {
654        let value = Shape::Nested(alloc::boxed::Box::new(Shape::Vector(alloc::vec![
655            1, 2, 3, 4
656        ])));
657        let wrapper = shape::Opaque::new(value.clone());
658        let recovered = wrapper.into_inner();
659        assert_eq!(recovered, value);
660    }
661
662    // ------------------------------------------------------------------
663    // 5. Trait forwarding — one module that opts in to every supported
664    //    derive, then a test per trait that exercises the impl.
665    // ------------------------------------------------------------------
666
667    #[derive(Debug, PartialEq, Eq, Hash, Clone, Default)]
668    struct AllTraits {
669        x: u64,
670        y: String,
671    }
672
673    // `Display` has no derive; the macro test relies on this manual impl
674    // delegating through the `Opaque`'s `Display` arm.
675    impl core::fmt::Display for AllTraits {
676        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
677            write!(f, "AllTraits(x={}, y={})", self.x, self.y)
678        }
679    }
680
681    // Full arm — derives then align.
682    obfuscate!(
683        type: AllTraits,
684        module: all_traits,
685        derives: [Send, Sync, Debug, Display, PartialEq, Eq, Hash, Clone, Default],
686        align: 8,
687    );
688
689    fn assert_send<T: Send>() {}
690    fn assert_sync<T: Sync>() {}
691
692    #[test]
693    fn opaque_is_send_and_sync_when_listed() {
694        assert_send::<all_traits::Opaque>();
695        assert_sync::<all_traits::Opaque>();
696    }
697
698    #[test]
699    fn opaque_debug_forwards_to_inner() {
700        let v = AllTraits {
701            x: 42,
702            y: String::from("hello"),
703        };
704        let w = all_traits::Opaque::new(v.clone());
705        assert_eq!(alloc::format!("{:?}", w), alloc::format!("{:?}", v));
706    }
707
708    #[test]
709    fn opaque_debug_alternate_format_forwards_to_inner() {
710        // `{:#?}` exercises the pretty-printer; the macro forwards the
711        // `Formatter` so flags/width/precision survive.
712        let v = AllTraits {
713            x: 42,
714            y: String::from("hello"),
715        };
716        let w = all_traits::Opaque::new(v.clone());
717        assert_eq!(alloc::format!("{:#?}", w), alloc::format!("{:#?}", v));
718    }
719
720    #[test]
721    fn opaque_display_forwards_to_inner() {
722        let v = AllTraits {
723            x: 7,
724            y: String::from("world"),
725        };
726        let w = all_traits::Opaque::new(v.clone());
727        assert_eq!(alloc::format!("{}", w), alloc::format!("{}", v));
728        assert_eq!(alloc::format!("{}", w), "AllTraits(x=7, y=world)");
729    }
730
731    #[test]
732    fn opaque_eq_marker_when_listed() {
733        // `Eq` is a marker — exercise it by demanding the bound at the
734        // type level. Compiles iff the macro emitted the impl.
735        fn assert_eq_bound<T: Eq>() {}
736        assert_eq_bound::<all_traits::Opaque>();
737    }
738
739    #[test]
740    fn opaque_partial_eq_forwards_to_inner() {
741        let a = all_traits::Opaque::new(AllTraits {
742            x: 1,
743            y: String::from("a"),
744        });
745        let b = all_traits::Opaque::new(AllTraits {
746            x: 1,
747            y: String::from("a"),
748        });
749        let c = all_traits::Opaque::new(AllTraits {
750            x: 2,
751            y: String::from("a"),
752        });
753        assert_eq!(a, b);
754        assert_ne!(a, c);
755    }
756
757    #[test]
758    fn opaque_hash_forwards_to_inner() {
759        use core::hash::{Hash, Hasher};
760
761        // Tiny deterministic hasher so this stays no_std-friendly.
762        struct Fnv(u64);
763        impl Hasher for Fnv {
764            fn finish(&self) -> u64 {
765                self.0
766            }
767            fn write(&mut self, bytes: &[u8]) {
768                for &b in bytes {
769                    self.0 = self.0.wrapping_mul(0x100000001b3).wrapping_add(b as u64);
770                }
771            }
772        }
773
774        let v = AllTraits {
775            x: 1,
776            y: String::from("a"),
777        };
778        let w = all_traits::Opaque::new(v.clone());
779
780        let mut h_inner = Fnv(0xcbf29ce484222325);
781        v.hash(&mut h_inner);
782        let mut h_opaque = Fnv(0xcbf29ce484222325);
783        w.hash(&mut h_opaque);
784
785        assert_eq!(h_inner.finish(), h_opaque.finish());
786    }
787
788    #[test]
789    fn opaque_clone_produces_independent_copy() {
790        let original = all_traits::Opaque::new(AllTraits {
791            x: 1,
792            y: String::from("hi"),
793        });
794        let cloned = original.clone();
795        assert_eq!(original, cloned);
796        // Storage is independent — distinct addresses.
797        let original_ptr = original.as_ref() as *const _ as usize;
798        let cloned_ptr = cloned.as_ref() as *const _ as usize;
799        assert_ne!(original_ptr, cloned_ptr);
800    }
801
802    #[test]
803    fn opaque_clone_deep_copies_owned_heap_data() {
804        // Confirm the inner's Clone really ran (not a shallow byte copy):
805        // mutating the original's String must not affect the clone.
806        let mut original = all_traits::Opaque::new(AllTraits {
807            x: 0,
808            y: String::from("first"),
809        });
810        let cloned = original.clone();
811        original.as_mut().y = String::from("mutated");
812        assert_eq!(cloned.as_ref().y, "first");
813    }
814
815    #[test]
816    fn opaque_default_constructs_default_inner() {
817        let w: all_traits::Opaque = Default::default();
818        assert_eq!(w.as_ref(), &AllTraits::default());
819    }
820
821    // ------------------------------------------------------------------
822    // 6. Zero-sized type — degenerate case.
823    // ------------------------------------------------------------------
824
825    #[derive(Debug, PartialEq, Eq, Clone, Default)]
826    struct Zst;
827
828    obfuscate!(
829        type: Zst,
830        module: zst,
831        derives: [Debug, PartialEq, Eq, Clone, Default],
832        // Zst has alignment 1, but the slot type (`*mut ()`) has
833        // pointer alignment, which becomes the floor. Declaring 8
834        // honors `max(align_of::<Zst>(), align_of::<*mut ()>())`.
835        align: 8,
836    );
837
838    #[test]
839    fn zero_sized_type_round_trip() {
840        let wrapper = zst::Opaque::new(Zst);
841        assert_eq!(wrapper.as_ref(), &Zst);
842        assert_eq!(wrapper.clone().into_inner(), Zst);
843    }
844}