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}