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