gel_protogen/
gen.rs

1/// Performs a first-pass parse on a struct, filling out some additional
2/// metadata that makes the jobs of further macro passes much simpler.
3///
4/// This macro takes a `next` parameter which allows you to funnel the
5/// structured data from the macro into the next macro.
6///
7/// This is a "push-down automation" and refers to how metadata and parsed
8/// information are "pushed down" through the macro’s recursive structure. Each
9/// level of the macro adds its own layer of processing and metadata
10/// accumulation, eventually leading to the final output.
11///
12/// It begins by extracting and analyzing the fields of the `struct`, capturing
13/// associated metadata such as attributes and types. This macro takes a `next`
14/// parameter, which is another macro to be invoked after the current one
15/// completes its task, allowing for a seamless chaining of macros where each
16/// one builds upon the results of the previous.
17///
18/// The macro first makes some initial classifications of the fields based on
19/// their types, then processes presence or absence of values, and finally
20/// handles missing documentation.
21///
22/// As it processes each field, the macro recursively calls itself, accumulating
23/// metadata and updating the state.
24///
25/// Once all fields have been processed, the macro enters the final stage, where
26/// it reconstructs an enriched `struct`-like data blob using the accumulated
27/// metadata. It then passes this enriched `struct` to the `next` macro for
28/// further processing.
29#[doc(hidden)]
30#[macro_export]
31macro_rules! struct_elaborate {
32    (
33        $next:ident $( ($($next_args:tt)*) )? =>
34        $( #[ $sdoc:meta ] )*
35        struct $name:ident <$lt:lifetime> $(: $super:ident)? {
36            $(
37                $( #[ doc = $fdoc:literal ] )* $field:ident :
38                    $ty:ty
39                    $( = $value:literal)?
40            ),*
41            $(,)?
42        }
43    ) => {
44        // paste! is necessary here because it allows us to re-interpret a "ty"
45        // as an explicit type pattern below.
46        $crate::paste!($crate::struct_elaborate!(__builder_type__
47            fields($(
48                [
49                    // Note that we double the type so we can re-use some output
50                    // patterns in `__builder_type__`
51                    type( $ty )( $ty ),
52                    value($($value)?),
53                    docs($($fdoc)*),
54                    name($field),
55                ]
56            )*)
57            // Accumulator for field data.
58            accum()
59            // Save the original struct parts so we can build the remainder of
60            // the struct at the end.
61            original($next $( ($($next_args)*) )?
62                => $(#[$sdoc])* struct $name <$lt> $(: $super)? {})
63        ););
64    };
65
66    // End of push-down automation - jumps to `__finalize__`
67    (__builder_type__ fields() accum($($faccum:tt)*) original($($original:tt)*)) => {
68        $crate::struct_elaborate!(__finalize__ accum($($faccum)*) original($($original)*));
69    };
70
71    // Translate 'len' to Length (with auto value).
72    (__builder_type__ fields([type(len)(len), value(), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
73        $crate::struct_elaborate!(__builder_docs__ fields([type($crate::prelude::Length), value(auto=auto), $($rest)*] $($frest)*) $($srest)*);
74    };
75    // Translate 'len' to Length (with a value present).
76    (__builder_type__ fields([type(len)(len), value($($value:tt)+), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
77        $crate::struct_elaborate!(__builder_docs__ fields([type($crate::prelude::Length), value(value=($($value)*)), $($rest)*] $($frest)*) $($srest)*);
78    };
79    // Translate fixed-size arrays to FixedArray.
80    (__builder_type__ fields([type([$elem:ty; $len:literal])($ty:ty), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
81        $crate::struct_elaborate!(__builder_value__ fields([type([$elem;$len]), $($rest)*] $($frest)*) $($srest)*);
82    };
83
84    // Fallback for other types - variable sized
85    (__builder_type__ fields([type($ty:ty)($ty2:ty), $($rest:tt)*] $($frest:tt)*) $($srest:tt)*) => {
86        $crate::struct_elaborate!(__builder_value__ fields([type($ty), $($rest)*] $($frest)*) $($srest)*);
87    };
88
89    // Next, mark the presence or absence of a value
90    (__builder_value__ fields([
91        type($ty:ty), value(), $($rest:tt)*
92    ] $($frest:tt)*) $($srest:tt)*) => {
93        $crate::struct_elaborate!(__builder_docs__ fields([type($ty), value(no_value=no_value), $($rest)*] $($frest)*) $($srest)*);
94    };
95    (__builder_value__ fields([
96        type($ty:ty), value($($value:tt)+), $($rest:tt)*
97    ] $($frest:tt)*) $($srest:tt)*) => {
98        $crate::struct_elaborate!(__builder_docs__ fields([type($ty), value(value=($($value)*)), $($rest)*] $($frest)*) $($srest)*);
99    };
100
101    // Next, handle missing docs by generating a stand-in.
102    (__builder_docs__ fields([
103        type($ty:ty), value($($value:tt)*), docs(), name($field:ident), $($rest:tt)*
104    ] $($frest:tt)*) $($srest:tt)*) => {
105        $crate::struct_elaborate!(__builder__ fields([type($ty), value($($value)*), docs(concat!("`", stringify!($field), "` field.")), name($field), $($rest)*] $($frest)*) $($srest)*);
106    };
107    (__builder_docs__ fields([
108        type($ty:ty), value($($value:tt)*), docs($($fdoc:literal)+), $($rest:tt)*
109    ] $($frest:tt)*) $($srest:tt)*) => {
110        $crate::struct_elaborate!(__builder__ fields([type($ty), value($($value)*), docs(concat!($($fdoc)+)), $($rest)*] $($frest)*) $($srest)*);
111    };
112
113
114    // Push down the field to the accumulator
115    (__builder__ fields([
116        type($ty:ty), value($($value:tt)*), docs($fdoc:expr), name($field:ident), $($rest:tt)*
117    ] $($frest:tt)*) accum($($faccum:tt)*) original($($original:tt)*)) => {
118        $crate::struct_elaborate!(__builder_type__ fields($($frest)*) accum(
119            $($faccum)*
120            {
121                name($field),
122                type($ty),
123                value($($value)*),
124                docs($fdoc),
125            },
126        ) original($($original)*));
127    };
128
129    // Write the final "elaborated" struct into a call to the `next` macro.
130    (__finalize__
131        accum($($accum:tt)*) original($next:ident $( ($($next_args:tt)*) )? =>
132        $( #[ $sdoc:meta ] )* struct $name:ident <$lt:lifetime> $(: $super:ident)? {})
133    ) => {
134        $next ! (
135            $( $($next_args)* , )?
136            struct $name <$lt> {
137                super($($super)?),
138                docs($($sdoc),*),
139                fields(
140                    $($accum)*
141                ),
142            }
143        );
144    }
145}
146
147/// Generates a protocol definition from a Rust-like DSL.
148///
149/// LIMITATION: Enums must appear after structs.
150///
151/// ```nocompile
152/// struct Foo {
153///     bar: u8,
154///     baz: u16 = 123,
155/// }
156///
157/// #[repr(u8)]
158/// enum MyEnum {
159///     A = 1,
160///     B = 2,
161/// }
162/// ```
163#[doc(hidden)]
164#[macro_export]
165macro_rules! __protocol {
166    (
167        $( $( #[ doc = $sdoc:literal ] )*
168            struct $name:ident <$lt:lifetime> $(: $super:ident)? { $($struct:tt)+ }
169        )+
170        $(  #[repr($repr:ty)] $( #[ doc = $edoc:literal ] )*
171            enum $ename:ident { $($(#[$default:meta])? $emname:ident = $emvalue:literal),+ $(,)? }
172        )*
173    ) => {
174        use $crate::protocol_builder;
175        #[allow(unused)]
176        use $crate::prelude::*;
177
178        $(
179            $crate::paste!(
180                $crate::struct_elaborate!(protocol_builder(__struct__) => $( #[ doc = $sdoc ] )* struct $name <$lt> $(: $super)? { $($struct)+ } );
181                $crate::struct_elaborate!(protocol_builder(__meta__) => $( #[ doc = $sdoc ] )* struct $name <$lt> $(: $super)? { $($struct)+ } );
182                $crate::struct_elaborate!(protocol_builder(__builder__) => $( #[ doc = $sdoc ] )* struct $name <$lt> $(: $super)? { $($struct)+ } );
183            );
184        )+
185
186        $(
187            $crate::paste!(
188                $(#[doc = $edoc])*
189                #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
190                #[repr($repr)]
191                pub enum $ename {
192                    $($(#[$default])? $emname = $emvalue),+
193                }
194
195                impl $crate::prelude::EnumMeta for $ename {
196                    const VALUES: &'static [(&'static str, usize)] = &[
197                        $((stringify!($emname), $emvalue as _)),+
198                    ];
199                }
200
201                $crate::declare_type!(DataType, $ename, flags=[enum], {
202                });
203
204                impl<'a> $crate::prelude::DecoderFor<'a, $ename> for $ename {
205                    fn decode_for(buf: &mut &'a [u8]) -> Result<Self, ParseError> {
206                        let repr = <$repr as $crate::prelude::DecoderFor<$repr>>::decode_for(buf)?;
207
208                        match repr {
209                            $(
210                                $emvalue => Ok($ename::$emname),
211                            )+
212                            _ => Err(ParseError::InvalidData(stringify!($ename), repr as usize)),
213                        }
214                    }
215                }
216
217                impl $crate::prelude::EncoderFor<$ename> for $ename {
218                    fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
219                        <$repr as $crate::prelude::EncoderFor<$repr>>::encode_for(&(*self as $repr), buf);
220                    }
221                }
222
223                impl $crate::prelude::EncoderFor<$ename> for &'_ $ename {
224                    fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
225                        <$repr as $crate::prelude::EncoderFor<$repr>>::encode_for(&(**self as $repr), buf);
226                    }
227                }
228            );
229        )*
230    };
231}
232
233#[doc(inline)]
234pub use __protocol as protocol;
235
236/// Simple conditional macro to check whether values are present.
237#[macro_export]
238#[doc(hidden)]
239macro_rules! r#if {
240    (__is_empty__ [] {$($true:tt)*} else {$($false:tt)*}) => {
241        $($true)*
242    };
243    (__is_empty__ [$($x:tt)+] {$($true:tt)*} else {$($false:tt)*}) => {
244        $($false)*
245    };
246    (__has__ [$($x:tt)+] {$($true:tt)*}) => {
247        $($true)*
248    };
249    (__has__ [] {$($true:tt)*}) => {
250    };
251}
252
253#[doc(hidden)]
254#[macro_export]
255macro_rules! make_static {
256    (type=$ty:ty) => { $crate::type_mapper::map_types!(match $ty {
257        _T<'a> => _T<'static>,
258        _T<'a, _T2> => _T<'static, recurse!(_T2)>,
259        _T<'a, _T2, _T3> => _T<'static, recurse!(_T2), recurse!(_T3)>,
260        _T<_T2> => _T<recurse!(_T2)>,
261        _T<_T2, _T3> => _T<recurse!(_T2), recurse!(_T3)>,
262        _T => _T,
263    }) };
264}
265
266#[doc(hidden)]
267#[macro_export]
268macro_rules! protocol_builder {
269    (__struct__, struct $name:ident <$lt:lifetime> {
270        super($($super:ident)?),
271        docs($($sdoc:meta),*),
272        fields($({
273            name($field:ident),
274            type($type:ty),
275            value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
276            docs($fdoc:expr),
277            $($rest:tt)*
278        },)*),
279    }) => {
280        $crate::paste!(
281            $( #[$sdoc] )?
282            #[doc = concat!("\n\nAvailable fields: \n\n" $(
283                , " - [`", stringify!($field), "`](Self::", stringify!($field), "()): ", $fdoc,
284                $( "  (value = `", stringify!($value), "`)", )?
285                "\n\n"
286            )* )]
287            #[derive(Copy, Clone, Default)]
288            pub struct $name<$lt> {
289                pub(crate) buf: &$lt [u8],
290                $(
291                    $field: $type,
292                )*
293            }
294
295            impl PartialEq for $name<'_> {
296                fn eq(&self, other: &Self) -> bool {
297                    self.buf.eq(other.buf)
298                }
299            }
300
301            impl Eq for $name<'_> {}
302
303            impl std::fmt::Debug for $name<'_> {
304                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305                    let mut s = f.debug_struct(stringify!($name));
306                    $(
307                        s.field(stringify!($field), &self.$field);
308                    )*
309                    s.finish()
310                }
311            }
312
313            impl AsRef<[u8]> for $name<'_> {
314                fn as_ref(&self) -> &[u8] {
315                    self.buf.as_ref()
316                }
317            }
318
319            #[allow(unused)]
320            impl <'a> $name<'a> {
321                /// Checks the constant values for this struct to determine whether
322                /// this message matches.
323                #[inline]
324                pub const fn is_buffer(buf: &'a [u8]) -> bool {
325                    <Self as $crate::prelude::StructMeta>::FIELDS.matches_field_constants(buf)
326                }
327
328                /// Creates a new instance of this struct from a given buffer.
329                #[inline]
330                pub fn new(mut buf: &'a [u8]) -> Result<Self, ParseError> {
331                    let res = <$name<'a> as $crate::prelude::DecoderFor<$name<'a>>>::decode_for(&mut buf);
332                    if buf.len() > 0 {
333                        return Err(ParseError::TooLong(buf.len()));
334                    }
335                    res
336                }
337
338                $(
339                    #[doc = $fdoc]
340                    pub fn $field(&self) -> $type {
341                        self.$field
342                    }
343                )*
344
345                pub fn to_vec(self) -> Vec<u8> {
346                    self.buf.to_vec()
347                }
348            }
349        );
350    };
351
352    (__meta__, struct $name:ident <$lt:lifetime> {
353        super($($super:ident)?),
354        docs($($sdoc:meta),*),
355        fields($({
356            name($field:ident),
357            type($type:ty),
358            value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
359            docs($fdoc:expr),
360            $($rest:tt)*
361        },)*),
362    }) => {
363        $crate::paste!(
364            #[allow(unused)]
365            #[allow(non_camel_case_types)]
366            #[derive(Eq, PartialEq)]
367            #[repr(u8)]
368            enum [<$name Fields>] {
369                $(
370                    $field,
371                )*
372            }
373
374            /// Implements a trait containing the fields of the struct, allowing
375            /// us to compute some useful things.
376            impl <$lt> $crate::prelude::StructMeta for $name<$lt> {
377                const FIELDS: $crate::prelude::StructFields = $crate::prelude::StructFields::new(&
378                    $crate::prelude::StructFieldComputed::new([
379                        $(
380                            $crate::prelude::StructField {
381                                name: stringify!($field),
382                                meta: &(<$type as DataType>::META),
383                                value: $crate::r#if!(__is_empty__ [$($value)?] { None } else { Some($($value)? as usize) }),
384                            },
385                        )*
386                    ]));
387
388                type Struct<'__struct> = $name<'__struct>;
389
390                fn new<'__next_lifetime>(buf: &'__next_lifetime [u8]) -> Result<Self::Struct<'__next_lifetime>, ParseError> {
391                    Self::Struct::<'__next_lifetime>::new(buf)
392                }
393
394                fn to_vec(&self) -> Vec<u8> {
395                    self.buf.to_vec()
396                }
397            }
398
399            /// Implements a trait indicating that the struct has a fixed size.
400            /// This needs to be a trait-generic rather than and associated
401            /// constant for us to use elsewhere.
402            impl $crate::prelude::StructAttributeFixedSize<{<$name<'_> as $crate::prelude::StructMeta>::IS_FIXED_SIZE}> for $name<'_> {
403            }
404
405            /// Implements a trait indicating that the struct has a length field.
406            impl $crate::prelude::StructAttributeHasLengthField<{<$name<'_> as $crate::prelude::StructMeta>::HAS_LENGTH_FIELD}> for $name<'_> {
407            }
408
409            /// Implements a trait indicating that the struct has a field count.
410            impl $crate::prelude::StructAttributeFieldCount<{<$name<'_> as $crate::prelude::StructMeta>::FIELD_COUNT}> for $name<'_> {
411            }
412
413            $crate::declare_type!(DataType, $name<'a>, builder: [<$name Builder>], flags=[struct], {});
414
415            impl<'a> $crate::prelude::DecoderFor<'a, $name<'a>> for $name<'a> {
416                fn decode_for(buf: &mut &'a [u8]) -> Result<Self, ParseError> {
417                    let mut new = $name::default();
418                    let start_buf = *buf;
419                    $(
420                        new.$field = <$type as $crate::prelude::DecoderFor<$type>>::decode_for(buf)?;
421                    )*
422                    new.buf = start_buf;
423                    Ok(new)
424                }
425            }
426
427            impl<'a> $crate::prelude::EncoderFor<$name<'static>> for $name<'a> {
428                fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
429                    buf.write(&self.buf);
430                }
431            }
432        );
433    };
434
435    (__struct_builder__, $( #[$sdoc:meta] )? struct $orig_name:ident $name:ident<$lt:lifetime> $($use_default:ident)? ($(
436        (
437            docs($sfdoc:expr)
438            name($sfield:ident)
439            type($stype:ty)
440            generic($sgeneric:ident)
441            no_value($sno_value:ident)
442        )
443    )*)
444    fields($({
445        name($field:ident),
446        type($type:ty),
447        value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
448        docs($fdoc:expr)
449    },)*),
450     ) => {
451        #[derive(Debug, Default)]
452        pub struct $name<$($sgeneric = $crate::make_static!(type=$stype)),*> where $(
453            $sgeneric: $crate::prelude::EncoderFor<$crate::make_static!(type=$stype)>,
454        )* {
455        // Because of how macros may expand in the context of struct
456        // fields, we need to do a * repeat, then a ? repeat and
457        // somehow use $no_value in the remainder of the pattern.
458        $(
459            #[doc = $sfdoc]
460            pub $sfield: $sgeneric,
461        )*
462        }
463
464        impl <$($sgeneric),*> $crate::prelude::BuilderFor for $name<$($sgeneric),*> where $(
465            $sgeneric: $crate::prelude::EncoderFor<$crate::make_static!(type=$stype)>,
466        )* {
467            type Message = $orig_name<'static>;
468        }
469
470        impl <$($sgeneric),*> $crate::prelude::EncoderFor<$orig_name<'static>> for $name<$($sgeneric),*> where $(
471            $sgeneric: $crate::prelude::EncoderFor<$crate::make_static!(type=$stype)>,
472        )* {
473            fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
474                #[allow(unused)]
475                let value = self;
476                $(
477                    $crate::r#if!(__is_empty__ [$($value)?] {
478                        $crate::r#if!(__is_empty__ [$($auto)?] {
479                            // value is no_value (present in builder)
480                            $crate::prelude::EncoderFor::<$crate::make_static!(type=$type)>::encode_for(&value.$field, buf);
481                        } else {
482                            // value is auto (not present in builder)
483                            let auto_offset = buf.size();
484                            $crate::prelude::EncoderFor::<$crate::make_static!(type=$type)>::encode_for(&<$type as Default>::default(), buf);
485                        });
486                    } else {
487                        // value is set, not present in builder
488                        <$type as DataType>::encode_usize(buf, $($value)? as usize);
489                    });
490                )*
491
492                $(
493                    $crate::r#if!(__has__ [$($auto)?] {
494                        let len = (buf.size() - auto_offset) as u32;
495                        buf.write_rewind(auto_offset, &len.to_be_bytes());
496                    });
497                )*
498            }
499        }
500
501        impl <$($sgeneric),*> $crate::prelude::EncoderFor<$orig_name<'static>> for &'_ $name<$($sgeneric),*> where $(
502            $sgeneric: $crate::prelude::EncoderFor<$crate::make_static!(type=$stype)>,
503        )* {
504            fn encode_for(&self, buf: &mut $crate::BufWriter<'_>) {
505                <$name<$($sgeneric),*> as $crate::prelude::EncoderFor<$orig_name<'static>>>::encode_for(self, buf);
506            }
507        }
508    };
509
510    (__builder__, struct $name:ident <$lt:lifetime> {
511        super($($super:ident)?),
512        docs($($sdoc:meta),*),
513        fields($({
514            name($field:ident),
515            type($type:ty),
516            value($(value = ($value:expr))? $(no_value = $no_value:ident)? $(auto = $auto:ident)?),
517            docs($fdoc:expr),
518            $($rest:tt)*
519        },)*),
520    }) => {
521        $crate::paste!(
522            $crate::r#if!(__is_empty__ [$($($no_value)?)*] {
523                $crate::protocol_builder!(__struct_builder__, $( #[$sdoc] )? struct $name [<$name Builder>]<$lt> __use_default_to_construct
524                    ()
525                    fields($({
526                        name($field),
527                        type($type),
528                        value($(value = ($value))? $(no_value = $no_value)? $(auto = $auto)?),
529                        docs($fdoc)
530                    },)*),
531                );
532            } else {
533                $crate::protocol_builder!(__struct_builder__, $( #[$sdoc] )? struct $name [<$name Builder>]<$lt>
534                    // Because of how macros may expand in the context of struct
535                    // fields, we need to do a * repeat, then a ? repeat and
536                    // somehow use $no_value in the remainder of the pattern.
537                    ($($(
538                        (
539                            docs($fdoc)
540                            name($field)
541                            type($type)
542                            generic([<$field:upper>])
543                            no_value($no_value)
544                        )
545                    )?)*) fields($({
546                        name($field),
547                        type($type),
548                        value($(value = ($value))? $(no_value = $no_value)? $(auto = $auto)?),
549                        docs($fdoc)
550                    },)*),
551                );
552            });
553        );
554    };
555}
556
557#[cfg(test)]
558mod tests {
559    use crate::prelude::StructAttributeHasLengthField;
560    use pretty_assertions::assert_eq;
561
562    mod fixed_only {
563        use super::*;
564
565        crate::protocol!(
566            struct FixedOnly<'a> {
567                a: u8,
568            }
569        );
570
571        static_assertions::assert_impl_any!(FixedOnly::<'static>: StructAttributeHasLengthField<false>);
572        static_assertions::assert_not_impl_any!(FixedOnly::<'static>: StructAttributeHasLengthField<true>);
573
574        static_assertions::assert_impl_all!(FixedOnly<'static>: DecoderFor<'static, FixedOnly<'static>>, EncoderFor<FixedOnly<'static>>);
575    }
576
577    mod fixed_only_value {
578        crate::protocol!(
579            struct FixedOnlyValue <'a> {
580                a: u8 = 1,
581            }
582        );
583    }
584
585    mod mixed {
586        crate::protocol!(
587            struct Mixed <'a> {
588                a: u8 = 1,
589                s: ZTString<'a>,
590            }
591        );
592    }
593
594    mod docs {
595        crate::protocol!(
596            /// Docs
597            struct Docs <'a> {
598                /// Docs
599                a: u8 = 1,
600                /// Docs
601                s: ZTString<'a>,
602            }
603        );
604    }
605
606    mod length {
607        use super::*;
608
609        crate::protocol!(
610            struct WithLength<'a> {
611                a: u8,
612                l: len,
613            }
614        );
615
616        static_assertions::assert_impl_any!(WithLength::<'static>: StructAttributeHasLengthField<true>);
617        static_assertions::assert_not_impl_any!(WithLength::<'static>: StructAttributeHasLengthField<false>);
618    }
619
620    mod array {
621        crate::protocol!(
622            struct StaticArray<'a> {
623                a: u8,
624                l: [u8; 4],
625            }
626        );
627    }
628
629    mod string {
630        crate::protocol!(
631            struct HasLString<'a> {
632                s: LString<'a>,
633            }
634        );
635    }
636
637    mod has_enum {
638        crate::protocol!(
639            struct HasEnum<'a> {
640                e: MyEnum,
641            }
642
643            #[repr(u8)]
644            enum MyEnum {
645                #[default]
646                A = 1,
647                B = 2,
648            }
649        );
650    }
651
652    macro_rules! assert_stringify {
653        (($($struct:tt)*), ($($expected:tt)*)) => {
654            $crate::struct_elaborate!(assert_stringify(__internal__ ($($expected)*)) => $($struct)*);
655        };
656        (__internal__ ($($expected:tt)*), $($struct:tt)*) => {
657            // We don't want whitespace to impact this comparison
658            if stringify!($($struct)*).replace(char::is_whitespace, "") != stringify!($($expected)*).replace(char::is_whitespace, "") {
659                assert_eq!(stringify!($($struct)*), stringify!($($expected)*));
660            }
661        };
662    }
663
664    #[test]
665    fn empty_struct() {
666        assert_stringify!((struct Foo <'a> {}), (struct Foo <'a> { super (), docs(), fields(), }));
667    }
668
669    #[test]
670    fn fixed_size_fields() {
671        assert_stringify!((struct Foo<'a>  {
672                    a: u8,
673                    b: u8,
674                }), (struct Foo<'a>
675        {
676            super (),
677            docs(),
678            fields({
679                name(a), type (u8), value(no_value = no_value),
680                docs(concat!("`", stringify! (a), "` field.")),
681            },
682            {
683                name(b), type (u8), value(no_value = no_value),
684                docs(concat!("`", stringify! (b), "` field.")),
685            },),
686        }));
687    }
688
689    #[test]
690    fn mixed_fields() {
691        assert_stringify!((struct Foo<'a> {
692                    a: u8,
693                    l: len,
694                    s: ZTString,
695                    c: i16,
696                    d: [u8; 4],
697                    e: ZTArray<ZTString>,
698                }), (struct Foo<'a>
699        {
700            super (),
701            docs(),
702            fields({
703                name(a), type (u8), value(no_value = no_value),
704                docs(concat!("`", stringify! (a), "` field.")),
705            },
706            {
707                name(l), type ($crate::prelude::Length),
708                value(auto = auto), docs(concat!("`", stringify! (l), "` field.")),
709            },
710            {
711                name(s), type (ZTString),
712                value(no_value = no_value),
713                docs(concat!("`", stringify! (s), "` field.")),
714            },
715            {
716                name(c), type (i16), value(no_value = no_value),
717                docs(concat!("`", stringify! (c), "` field.")),
718            },
719            {
720                name(d), type ([u8; 4]),
721                value(no_value = no_value),
722                docs(concat!("`", stringify! (d), "` field.")),
723            },
724            {
725                name(e), type (ZTArray<ZTString>),
726                value(no_value = no_value),
727                docs(concat!("`", stringify! (e), "` field.")),
728            },
729        ),
730        }));
731    }
732}