Skip to main content

photon_ring_derive/
lib.rs

1// Copyright 2026 Photon Ring Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Derive macros for [`photon_ring::Pod`] and [`photon_ring::Message`].
5//!
6//! ## `Pod` derive
7//!
8//! ```ignore
9//! #[derive(photon_ring::Pod)]
10//! struct Quote {
11//!     price: f64,
12//!     volume: u32,
13//! }
14//! ```
15//!
16//! This generates compile-time assertions that every field implements `Pod`,
17//! plus `unsafe impl photon_ring::Pod for Quote {}`.
18//!
19//! **Note:** The macro does *not* add `#[repr(C)]` or `Clone`/`Copy` derives.
20//! You must add those yourself for the `Pod` contract to hold.
21//!
22//! ## `Message` derive
23//!
24//! ```ignore
25//! #[derive(photon_ring::Message)]
26//! struct Order {
27//!     price: f64,
28//!     qty: u32,
29//!     #[photon(as_enum)]
30//!     side: Side,        // any #[repr(u8)] enum — requires #[photon(as_enum)]
31//!     filled: bool,
32//!     tag: Option<u32>,
33//! }
34//! ```
35//!
36//! Generates a Pod-compatible wire struct (`OrderWire`) plus `From`
37//! conversions in both directions. See [`derive_message`] for details.
38
39use proc_macro::TokenStream;
40use proc_macro2::Span;
41use quote::{format_ident, quote};
42use syn::{
43    parse_macro_input, Data, DeriveInput, Fields, GenericArgument, Meta, PathArguments, Type,
44};
45
46/// Derive `Pod` for a struct.
47///
48/// Requirements:
49/// - Must be a struct (not enum or union).
50/// - All fields must implement `Pod`.
51/// - The user must add `#[repr(C)]`, `Clone`, and `Copy` themselves;
52///   the macro only emits field assertions and `unsafe impl Pod`.
53///
54/// # Example
55///
56/// ```ignore
57/// #[derive(photon_ring::Pod)]
58/// struct Tick {
59///     price: f64,
60///     volume: u32,
61///     _pad: u32,
62/// }
63/// ```
64#[proc_macro_derive(Pod)]
65pub fn derive_pod(input: TokenStream) -> TokenStream {
66    let input = parse_macro_input!(input as DeriveInput);
67    let name = &input.ident;
68    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
69
70    // Verify #[repr(C)] is present
71    let has_repr_c = input.attrs.iter().any(|attr| {
72        if !attr.path().is_ident("repr") {
73            return false;
74        }
75        let mut found = false;
76        if let Meta::List(list) = &attr.meta {
77            let _ = list.parse_nested_meta(|nested| {
78                if nested.path.is_ident("C") {
79                    found = true;
80                }
81                Ok(())
82            });
83        }
84        found
85    });
86    if !has_repr_c {
87        return syn::Error::new_spanned(
88            &input.ident,
89            "Pod can only be derived for #[repr(C)] structs",
90        )
91        .to_compile_error()
92        .into();
93    }
94
95    // Only structs are supported
96    let fields = match &input.data {
97        Data::Struct(s) => match &s.fields {
98            Fields::Named(f) => f.named.iter().collect::<Vec<_>>(),
99            Fields::Unnamed(f) => f.unnamed.iter().collect::<Vec<_>>(),
100            Fields::Unit => vec![],
101        },
102        _ => {
103            return syn::Error::new_spanned(&input.ident, "Pod can only be derived for structs")
104                .to_compile_error()
105                .into();
106        }
107    };
108
109    // Generate compile-time assertions that every field is Pod
110    let field_assertions = fields.iter().map(|f| {
111        let ty = &f.ty;
112        quote! {
113            const _: () = {
114                fn _assert_pod<T: photon_ring::Pod>() {}
115                fn _check() { _assert_pod::<#ty>(); }
116            };
117        }
118    });
119
120    let expanded = quote! {
121        // Compile-time field checks
122        #(#field_assertions)*
123
124        // Safety: all fields verified to be Pod via compile-time assertions above.
125        // The derive macro only applies to structs, and Pod requires that every
126        // bit pattern is valid — which holds when all fields are Pod.
127        unsafe impl #impl_generics photon_ring::Pod for #name #ty_generics #where_clause {}
128    };
129
130    TokenStream::from(expanded)
131}
132
133// ---------------------------------------------------------------------------
134// Message derive
135// ---------------------------------------------------------------------------
136
137/// Classification of a field type for wire conversion.
138enum FieldKind {
139    /// Numeric or array — passes through unchanged.
140    Passthrough,
141    /// `bool` → `u8`.
142    Bool,
143    /// `usize` → `u64`.
144    Usize,
145    /// `isize` → `i64`.
146    Isize,
147    /// `Option<T>` where T is an unsigned ≤64-bit integer type (u8, u16, u32, u64).
148    /// Wire struct gets two fields: `X_value: u64` and `X_has: u8`.
149    /// Stores the inner type for the back-conversion cast.
150    OptionUnsignedInt(Type),
151    /// `Option<T>` where T is a signed ≤64-bit integer type (i8, i16, i32, i64).
152    /// Wire struct gets two fields: `X_value: i64` and `X_has: u8`.
153    /// Stores the inner type for the back-conversion cast.
154    OptionSignedInt(Type),
155    /// `Option<bool>` — wire struct gets `X_value: u8` and `X_has: u8`.
156    OptionBool,
157    /// `Option<u128>` — wire struct gets `X_value: u128` and `X_has: u8`.
158    OptionU128,
159    /// `Option<i128>` — wire struct gets `X_value: u128` and `X_has: u8`.
160    OptionI128,
161    /// `Option<usize>` — wire struct gets `X_value: u64` and `X_has: u8`.
162    OptionUsize,
163    /// `Option<isize>` — wire struct gets `X_value: i64` and `X_has: u8`.
164    OptionIsize,
165    /// `Option<f32>` — wire struct gets `X_value: u32` (bit-encoded) and `X_has: u8`.
166    OptionF32,
167    /// `Option<f64>` — wire struct gets `X_value: u64` (bit-encoded) and `X_has: u8`.
168    OptionF64,
169    /// A `#[repr(u8)]` enum, explicitly marked with `#[photon(as_enum)]` → `u8`.
170    Enum,
171    /// Unrecognized type — will emit a compile error.
172    Unsupported,
173    /// Unsupported `Option<T>` inner type — will emit a compile error.
174    UnsupportedOption(String),
175}
176
177/// Returns the type name string for a simple path type, or `None`.
178fn type_name(ty: &Type) -> Option<String> {
179    if let Type::Path(p) = ty {
180        if let Some(seg) = p.path.segments.last() {
181            return Some(seg.ident.to_string());
182        }
183    }
184    None
185}
186
187/// Classify a field's type into a [`FieldKind`].
188fn classify(ty: &Type) -> FieldKind {
189    match ty {
190        // Arrays `[T; N]` — passthrough (must be Pod).
191        Type::Array(_) => FieldKind::Passthrough,
192
193        Type::Path(p) => {
194            let seg = match p.path.segments.last() {
195                Some(s) => s,
196                None => return FieldKind::Unsupported,
197            };
198            let id = seg.ident.to_string();
199
200            match id.as_str() {
201                // Numerics — passthrough
202                "u8" | "u16" | "u32" | "u64" | "u128" | "i8" | "i16" | "i32" | "i64" | "i128"
203                | "f32" | "f64" => FieldKind::Passthrough,
204
205                "bool" => FieldKind::Bool,
206                "usize" => FieldKind::Usize,
207                "isize" => FieldKind::Isize,
208
209                "Option" => {
210                    // Extract inner type from Option<T>
211                    if let PathArguments::AngleBracketed(args) = &seg.arguments {
212                        if let Some(GenericArgument::Type(inner)) = args.args.first() {
213                            let name = type_name(inner).unwrap_or_default();
214                            return match name.as_str() {
215                                "bool" => FieldKind::OptionBool,
216                                "f32" => FieldKind::OptionF32,
217                                "f64" => FieldKind::OptionF64,
218                                "u128" => FieldKind::OptionU128,
219                                "i128" => FieldKind::OptionI128,
220                                "usize" => FieldKind::OptionUsize,
221                                "isize" => FieldKind::OptionIsize,
222                                "u8" | "u16" | "u32" | "u64" => {
223                                    FieldKind::OptionUnsignedInt(inner.clone())
224                                }
225                                "i8" | "i16" | "i32" | "i64" => {
226                                    FieldKind::OptionSignedInt(inner.clone())
227                                }
228                                _ => FieldKind::UnsupportedOption(name),
229                            };
230                        }
231                    }
232                    FieldKind::UnsupportedOption(String::new())
233                }
234
235                // Anything else — unrecognized, require explicit attribute
236                _ => FieldKind::Unsupported,
237            }
238        }
239
240        _ => FieldKind::Unsupported,
241    }
242}
243
244/// Derive a Pod-compatible wire struct with `From` conversions.
245///
246/// Given a struct with fields that may include `bool`, `Option<numeric>`,
247/// `usize`/`isize`, and `#[repr(u8)]` enums, generates:
248///
249/// 1. **`{Name}Wire`** — a `#[repr(C)] Clone + Copy` struct with all fields
250///    converted to Pod-safe types, plus `unsafe impl Pod`.
251/// 2. **`From<Name> for {Name}Wire`** — converts the domain struct to wire.
252/// 3. **`{Name}Wire::into_domain(self) -> Name`** — converts the wire struct
253///    back. This is an `unsafe` method for structs containing enum fields
254///    (since the enum discriminant is not validated), or a safe `From` impl
255///    for structs without enum fields.
256///
257/// # Field type mappings
258///
259/// | Source type | Wire type | To wire | From wire |
260/// |---|---|---|---|
261/// | `f32`, `f64`, `u8`..`u128`, `i8`..`i128` | same | passthrough | passthrough |
262/// | `usize` | `u64` | `as u64` | `as usize` |
263/// | `isize` | `i64` | `as i64` | `as isize` |
264/// | `bool` | `u8` | `if v { 1 } else { 0 }` | `v != 0` |
265/// | `Option<T>` (T: unsigned ≤64-bit) | `X_value: u64, X_has: u8` | `Some(v) => (v as u64, 1), None => (0, 0)` | `has != 0 => Some(value as T), else None` |
266/// | `Option<T>` (T: signed ≤64-bit) | `X_value: i64, X_has: u8` | `Some(v) => (v as i64, 1), None => (0, 0)` | `has != 0 => Some(value as T), else None` |
267/// | `Option<u128>` | `X_value: u128, X_has: u8` | `Some(v) => (v, 1), None => (0, 0)` | `has != 0 => Some(value), else None` |
268/// | `Option<i128>` | `X_value: u128, X_has: u8` | `Some(v) => (v as u128, 1), None => (0, 0)` | `has != 0 => Some(value as i128), else None` |
269/// | `Option<usize>` | `X_value: u64, X_has: u8` | `Some(v) => (v as u64, 1), None => (0, 0)` | `has != 0 => Some(value as usize), else None` |
270/// | `Option<isize>` | `X_value: i64, X_has: u8` | `Some(v) => (v as i64, 1), None => (0, 0)` | `has != 0 => Some(value as isize), else None` |
271/// | `Option<f32>` | `X_value: u32, X_has: u8` | `Some(v) => (v.to_bits(), 1), None => (0, 0)` | `has != 0 => Some(f32::from_bits(value)), else None` |
272/// | `Option<f64>` | `X_value: u64, X_has: u8` | `Some(v) => (v.to_bits(), 1), None => (0, 0)` | `has != 0 => Some(f64::from_bits(value)), else None` |
273/// | `[T; N]` (T: Pod) | same | passthrough | passthrough |
274/// | `#[photon(as_enum)] field: E` | `u8` | `v as u8` | `transmute(v)` (unsafe) |
275///
276/// # Enum fields
277///
278/// Enum fields **must** be annotated with `#[photon(as_enum)]` to opt in
279/// to the `u8` wire encoding. Without this attribute, unrecognized types
280/// produce a compile error. The enum must have `#[repr(u8)]` — the macro
281/// emits a compile-time `size_of` check to enforce this.
282///
283/// Enum fields are stored as raw `u8` on the wire. Converting back requires
284/// that the byte holds a valid discriminant. Because the macro cannot verify
285/// enum variants at compile time, structs with enum fields generate an
286/// `unsafe fn into_domain(self) -> DomainType` method on the wire struct
287/// instead of a safe `From` impl. Callers must ensure enum fields contain
288/// valid discriminants (which is always the case when the wire data was
289/// produced by a valid domain value via `From<Domain> for Wire`).
290///
291/// # Example
292///
293/// ```ignore
294/// #[repr(u8)]
295/// #[derive(Clone, Copy)]
296/// enum Side { Buy = 0, Sell = 1 }
297///
298/// #[derive(photon_ring::Message)]
299/// struct Order {
300///     price: f64,
301///     qty: u32,
302///     #[photon(as_enum)]
303///     side: Side,
304///     filled: bool,
305///     tag: Option<u32>,
306/// }
307/// // Generates: OrderWire, From<Order> for OrderWire,
308/// //            OrderWire::into_domain (unsafe, due to enum field)
309/// ```
310#[proc_macro_derive(Message, attributes(photon))]
311pub fn derive_message(input: TokenStream) -> TokenStream {
312    let input = parse_macro_input!(input as DeriveInput);
313    let name = &input.ident;
314    let wire_name = format_ident!("{}Wire", name);
315
316    // Only named structs are supported
317    let fields = match &input.data {
318        Data::Struct(s) => match &s.fields {
319            Fields::Named(f) => f.named.iter().collect::<Vec<_>>(),
320            _ => {
321                return syn::Error::new_spanned(
322                    &input.ident,
323                    "Message can only be derived for structs with named fields",
324                )
325                .to_compile_error()
326                .into();
327            }
328        },
329        _ => {
330            return syn::Error::new_spanned(
331                &input.ident,
332                "Message can only be derived for structs",
333            )
334            .to_compile_error()
335            .into();
336        }
337    };
338
339    let mut wire_fields = Vec::new();
340    let mut to_wire = Vec::new();
341    let mut from_wire = Vec::new();
342    let mut assertions = Vec::new();
343    let mut has_enum_fields = false;
344    let mut has_usize_isize = false;
345
346    for field in &fields {
347        let fname = field.ident.as_ref().unwrap();
348        let fty = &field.ty;
349
350        // Check for #[photon(as_enum)] attribute
351        let is_explicit_enum = field.attrs.iter().any(|attr| {
352            if attr.path().is_ident("photon") {
353                if let Ok(meta) = attr.parse_args::<syn::Ident>() {
354                    return meta == "as_enum";
355                }
356            }
357            false
358        });
359
360        let kind = if is_explicit_enum {
361            FieldKind::Enum
362        } else {
363            classify(fty)
364        };
365
366        match kind {
367            FieldKind::Passthrough => {
368                wire_fields.push(quote! { pub #fname: #fty });
369                to_wire.push(quote! { #fname: src.#fname });
370                from_wire.push(quote! { #fname: src.#fname });
371            }
372            FieldKind::Bool => {
373                wire_fields.push(quote! { pub #fname: u8 });
374                to_wire.push(quote! { #fname: if src.#fname { 1 } else { 0 } });
375                from_wire.push(quote! { #fname: src.#fname != 0 });
376            }
377            FieldKind::Usize => {
378                has_usize_isize = true;
379                wire_fields.push(quote! { pub #fname: u64 });
380                to_wire.push(quote! { #fname: src.#fname as u64 });
381                from_wire.push(quote! { #fname: src.#fname as usize });
382            }
383            FieldKind::Isize => {
384                has_usize_isize = true;
385                wire_fields.push(quote! { pub #fname: i64 });
386                to_wire.push(quote! { #fname: src.#fname as i64 });
387                from_wire.push(quote! { #fname: src.#fname as isize });
388            }
389            FieldKind::OptionUnsignedInt(inner) => {
390                let value_field = format_ident!("{}_value", fname);
391                let has_field = format_ident!("{}_has", fname);
392                wire_fields.push(quote! { pub #value_field: u64 });
393                wire_fields.push(quote! { pub #has_field: u8 });
394                to_wire.push(quote! {
395                    #value_field: match src.#fname {
396                        Some(v) => v as u64,
397                        None => 0,
398                    }
399                });
400                to_wire.push(quote! {
401                    #has_field: if src.#fname.is_some() { 1 } else { 0 }
402                });
403                from_wire.push(quote! {
404                    #fname: if src.#has_field != 0 {
405                        Some(src.#value_field as #inner)
406                    } else {
407                        None
408                    }
409                });
410            }
411            FieldKind::OptionSignedInt(inner) => {
412                let value_field = format_ident!("{}_value", fname);
413                let has_field = format_ident!("{}_has", fname);
414                wire_fields.push(quote! { pub #value_field: i64 });
415                wire_fields.push(quote! { pub #has_field: u8 });
416                to_wire.push(quote! {
417                    #value_field: match src.#fname {
418                        Some(v) => v as i64,
419                        None => 0,
420                    }
421                });
422                to_wire.push(quote! {
423                    #has_field: if src.#fname.is_some() { 1 } else { 0 }
424                });
425                from_wire.push(quote! {
426                    #fname: if src.#has_field != 0 {
427                        Some(src.#value_field as #inner)
428                    } else {
429                        None
430                    }
431                });
432            }
433            FieldKind::OptionBool => {
434                let value_field = format_ident!("{}_value", fname);
435                let has_field = format_ident!("{}_has", fname);
436                wire_fields.push(quote! { pub #value_field: u8 });
437                wire_fields.push(quote! { pub #has_field: u8 });
438                to_wire.push(quote! {
439                    #value_field: match src.#fname {
440                        Some(v) => if v { 1 } else { 0 },
441                        None => 0,
442                    }
443                });
444                to_wire.push(quote! {
445                    #has_field: if src.#fname.is_some() { 1 } else { 0 }
446                });
447                from_wire.push(quote! {
448                    #fname: if src.#has_field != 0 {
449                        Some(src.#value_field != 0)
450                    } else {
451                        None
452                    }
453                });
454            }
455            FieldKind::OptionU128 => {
456                let value_field = format_ident!("{}_value", fname);
457                let has_field = format_ident!("{}_has", fname);
458                wire_fields.push(quote! { pub #value_field: u128 });
459                wire_fields.push(quote! { pub #has_field: u8 });
460                to_wire.push(quote! {
461                    #value_field: match src.#fname {
462                        Some(v) => v,
463                        None => 0,
464                    }
465                });
466                to_wire.push(quote! {
467                    #has_field: if src.#fname.is_some() { 1 } else { 0 }
468                });
469                from_wire.push(quote! {
470                    #fname: if src.#has_field != 0 {
471                        Some(src.#value_field)
472                    } else {
473                        None
474                    }
475                });
476            }
477            FieldKind::OptionI128 => {
478                let value_field = format_ident!("{}_value", fname);
479                let has_field = format_ident!("{}_has", fname);
480                wire_fields.push(quote! { pub #value_field: u128 });
481                wire_fields.push(quote! { pub #has_field: u8 });
482                to_wire.push(quote! {
483                    #value_field: match src.#fname {
484                        Some(v) => v as u128,
485                        None => 0,
486                    }
487                });
488                to_wire.push(quote! {
489                    #has_field: if src.#fname.is_some() { 1 } else { 0 }
490                });
491                from_wire.push(quote! {
492                    #fname: if src.#has_field != 0 {
493                        Some(src.#value_field as i128)
494                    } else {
495                        None
496                    }
497                });
498            }
499            FieldKind::OptionUsize => {
500                has_usize_isize = true;
501                let value_field = format_ident!("{}_value", fname);
502                let has_field = format_ident!("{}_has", fname);
503                wire_fields.push(quote! { pub #value_field: u64 });
504                wire_fields.push(quote! { pub #has_field: u8 });
505                to_wire.push(quote! {
506                    #value_field: match src.#fname {
507                        Some(v) => v as u64,
508                        None => 0,
509                    }
510                });
511                to_wire.push(quote! {
512                    #has_field: if src.#fname.is_some() { 1 } else { 0 }
513                });
514                from_wire.push(quote! {
515                    #fname: if src.#has_field != 0 {
516                        Some(src.#value_field as usize)
517                    } else {
518                        None
519                    }
520                });
521            }
522            FieldKind::OptionIsize => {
523                has_usize_isize = true;
524                let value_field = format_ident!("{}_value", fname);
525                let has_field = format_ident!("{}_has", fname);
526                wire_fields.push(quote! { pub #value_field: i64 });
527                wire_fields.push(quote! { pub #has_field: u8 });
528                to_wire.push(quote! {
529                    #value_field: match src.#fname {
530                        Some(v) => v as i64,
531                        None => 0,
532                    }
533                });
534                to_wire.push(quote! {
535                    #has_field: if src.#fname.is_some() { 1 } else { 0 }
536                });
537                from_wire.push(quote! {
538                    #fname: if src.#has_field != 0 {
539                        Some(src.#value_field as isize)
540                    } else {
541                        None
542                    }
543                });
544            }
545            FieldKind::OptionF32 => {
546                let value_field = format_ident!("{}_value", fname);
547                let has_field = format_ident!("{}_has", fname);
548                wire_fields.push(quote! { pub #value_field: u32 });
549                wire_fields.push(quote! { pub #has_field: u8 });
550                to_wire.push(quote! {
551                    #value_field: match src.#fname {
552                        Some(v) => v.to_bits(),
553                        None => 0,
554                    }
555                });
556                to_wire.push(quote! {
557                    #has_field: if src.#fname.is_some() { 1 } else { 0 }
558                });
559                from_wire.push(quote! {
560                    #fname: if src.#has_field != 0 {
561                        Some(f32::from_bits(src.#value_field))
562                    } else {
563                        None
564                    }
565                });
566            }
567            FieldKind::OptionF64 => {
568                let value_field = format_ident!("{}_value", fname);
569                let has_field = format_ident!("{}_has", fname);
570                wire_fields.push(quote! { pub #value_field: u64 });
571                wire_fields.push(quote! { pub #has_field: u8 });
572                to_wire.push(quote! {
573                    #value_field: match src.#fname {
574                        Some(v) => v.to_bits(),
575                        None => 0,
576                    }
577                });
578                to_wire.push(quote! {
579                    #has_field: if src.#fname.is_some() { 1 } else { 0 }
580                });
581                from_wire.push(quote! {
582                    #fname: if src.#has_field != 0 {
583                        Some(f64::from_bits(src.#value_field))
584                    } else {
585                        None
586                    }
587                });
588            }
589            FieldKind::Enum => {
590                has_enum_fields = true;
591                wire_fields.push(quote! { pub #fname: u8 });
592                to_wire.push(quote! { #fname: src.#fname as u8 });
593                from_wire.push(quote! {
594                    // SAFETY: This transmute converts a raw u8 back to the enum type.
595                    // This is sound ONLY when the byte contains a valid discriminant.
596                    // The wire struct should only be constructed via `From<DomainType>`,
597                    // which guarantees valid discriminants. Constructing the wire struct
598                    // from arbitrary bytes and calling `into_domain()` is undefined
599                    // behavior if any enum field holds an invalid discriminant.
600                    #fname: unsafe { core::mem::transmute::<u8, #fty>(src.#fname) }
601                });
602                // Compile-time assertion: enum must be 1 byte (#[repr(u8)])
603                let msg = format!(
604                    "Message derive: field `{}` has type `{}` which is not 1 byte. \
605                     Enum fields must have #[repr(u8)].",
606                    fname,
607                    quote! { #fty },
608                );
609                let msg_lit = syn::LitStr::new(&msg, Span::call_site());
610                assertions.push(quote! {
611                    const _: () = {
612                        assert!(
613                            core::mem::size_of::<#fty>() == 1,
614                            #msg_lit,
615                        );
616                    };
617                });
618            }
619            FieldKind::Unsupported => {
620                let msg = format!(
621                    "Unsupported field type `{}`. Use #[photon(as_enum)] for #[repr(u8)] enum fields, \
622                     or convert to a numeric type manually.",
623                    quote!(#fty),
624                );
625                return syn::Error::new_spanned(fty, msg).to_compile_error().into();
626            }
627            FieldKind::UnsupportedOption(inner_name) => {
628                let msg = format!(
629                    "Message derive: field `{}` has unsupported type `Option<{}>`. \
630                     Only Option<bool>, Option<integer>, Option<f32>, and Option<f64> \
631                     are supported.",
632                    fname, inner_name,
633                );
634                return syn::Error::new_spanned(fty, msg).to_compile_error().into();
635            }
636        }
637    }
638
639    // H5: Compile-time assertion that usize/isize fit in u64/i64 (documents
640    // the 64-bit assumption and fails loudly on platforms where it does not hold).
641    if has_usize_isize {
642        assertions.push(quote! {
643            const _: () = assert!(
644                core::mem::size_of::<usize>() <= core::mem::size_of::<u64>(),
645                "photon-ring Message derive requires usize to fit in u64",
646            );
647        });
648    }
649
650    // If the struct has enum fields, generate an unsafe `into_domain` method
651    // instead of a safe `From` impl to avoid exposing transmute through safe code.
652    let from_wire_impl = if has_enum_fields {
653        quote! {
654            impl #wire_name {
655                /// Convert wire struct back to domain struct.
656                ///
657                /// # Safety
658                ///
659                /// Enum fields are stored as raw `u8` and converted back via
660                /// `core::mem::transmute`. The caller **must** ensure every enum
661                /// field contains a valid discriminant value. This is guaranteed
662                /// when the wire struct was produced by `From<DomainType>` — but
663                /// constructing the wire struct from arbitrary bytes (e.g. reading
664                /// raw memory, deserialization) and calling this method is
665                /// **undefined behavior** if any enum field holds an invalid
666                /// discriminant.
667                #[inline]
668                pub unsafe fn into_domain(self) -> #name {
669                    let src = self;
670                    #name {
671                        #(#from_wire),*
672                    }
673                }
674            }
675        }
676    } else {
677        quote! {
678            impl From<#wire_name> for #name {
679                #[inline]
680                fn from(src: #wire_name) -> Self {
681                    #name {
682                        #(#from_wire),*
683                    }
684                }
685            }
686        }
687    };
688
689    // C3: Add a doc warning on the wire struct when it contains enum fields
690    let wire_struct_doc = if has_enum_fields {
691        quote! {
692            /// Auto-generated Pod-compatible wire struct for the domain type.
693            ///
694            /// # Warning
695            ///
696            /// This struct contains enum fields stored as raw `u8`. Constructing
697            /// it from arbitrary bytes (not via `From<DomainType>`) and then calling
698            /// `into_domain()` can cause **undefined behavior** if any enum field
699            /// holds an invalid discriminant value.
700        }
701    } else {
702        quote! {
703            /// Auto-generated Pod-compatible wire struct for the domain type.
704        }
705    };
706
707    let expanded = quote! {
708        // Compile-time assertions
709        #(#assertions)*
710
711        #wire_struct_doc
712        #[repr(C)]
713        #[derive(Clone, Copy)]
714        pub struct #wire_name {
715            #(#wire_fields),*
716        }
717
718        // Safety: all fields of the wire struct are plain numeric types
719        // (u8, u32, u64, f32, f64, etc.) where every bit pattern is valid.
720        unsafe impl photon_ring::Pod for #wire_name {}
721
722        impl From<#name> for #wire_name {
723            #[inline]
724            fn from(src: #name) -> Self {
725                #wire_name {
726                    #(#to_wire),*
727                }
728            }
729        }
730
731        #from_wire_impl
732    };
733
734    TokenStream::from(expanded)
735}