facet_macros_parse/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4/// Parse function signature shape
5#[cfg(feature = "function")]
6pub mod function;
7#[cfg(feature = "function")]
8pub use function::*;
9
10pub use unsynn::*;
11
12keyword! {
13    /// The "pub" keyword.
14    pub KPub = "pub";
15    /// The "struct" keyword.
16    pub KStruct = "struct";
17    /// The "enum" keyword.
18    pub KEnum = "enum";
19    /// The "doc" keyword.
20    pub KDoc = "doc";
21    /// The "repr" keyword.
22    pub KRepr = "repr";
23    /// The "crate" keyword.
24    pub KCrate = "crate";
25    /// The "in" keyword.
26    pub KIn = "in";
27    /// The "const" keyword.
28    pub KConst = "const";
29    /// The "where" keyword.
30    pub KWhere = "where";
31    /// The "mut" keyword.
32    pub KMut = "mut";
33    /// The "facet" keyword.
34    pub KFacet = "facet";
35    /// The "sensitive" keyword.
36    pub KSensitive = "sensitive";
37    /// The "invariants" keyword.
38    pub KInvariants = "invariants";
39    /// The "opaque" keyword.
40    pub KOpaque = "opaque";
41    /// The "deny_unknown_fields" keyword.
42    pub KDenyUnknownFields = "deny_unknown_fields";
43    /// The "default" keyword.
44    pub KDefault = "default";
45    /// The "transparent" keyword.
46    pub KTransparent = "transparent";
47    /// The "rename" keyword.
48    pub KRename = "rename";
49    /// The "rename_all" keyword.
50    pub KRenameAll = "rename_all";
51    /// The "flatten" keyword
52    pub KFlatten = "flatten";
53    /// The "child" keyword
54    pub KChild = "child";
55    /// The "skip_serializing" keyword.
56    pub KSkipSerializing = "skip_serializing";
57    /// The "skip_serializing_if" keyword.
58    pub KSkipSerializingIf = "skip_serializing_if";
59    /// The "type_tag" keyword.
60    pub KTypeTag = "type_tag";
61    /// The "deserialize_with" keyword.
62    pub KDeserializeWith = "deserialize_with";
63    /// The "serialize_with" keyword.
64    pub KSerializeWith = "serialize_with";
65}
66
67operator! {
68    /// Represents the '=' operator.
69    pub Eq = "=";
70    /// Represents the ';' operator.
71    pub Semi = ";";
72    /// Represents the apostrophe '\'' operator.
73    pub Apostrophe = "'";
74    /// Represents the double semicolon '::' operator.
75    pub DoubleSemicolon = "::";
76}
77
78/// Parses tokens and groups until `C` is found on the current token tree level.
79pub type VerbatimUntil<C> = Many<Cons<Except<C>, AngleTokenTree>>;
80
81/// Represents a module path, consisting of an optional path separator followed by
82/// a path-separator-delimited sequence of identifiers.
83pub type ModPath = Cons<Option<PathSep>, PathSepDelimited<Ident>>;
84
85/// Represents type bounds, consisting of a colon followed by tokens until
86/// a comma, equals sign, or closing angle bracket is encountered.
87pub type Bounds = Cons<Colon, VerbatimUntil<Either<Comma, Eq, Gt>>>;
88
89unsynn! {
90    /// Parses either a `TokenTree` or `<...>` grouping (which is not a [`Group`] as far as proc-macros
91    /// are concerned).
92    #[derive(Clone)]
93    pub struct AngleTokenTree(
94        #[allow(clippy::type_complexity)] // look,
95        pub Either<Cons<Lt, Vec<Cons<Except<Gt>, AngleTokenTree>>, Gt>, TokenTree>,
96    );
97
98    /// Represents an algebraic data type (ADT) declaration, which can be either a struct or enum.
99    pub enum AdtDecl {
100        /// A struct ADT variant.
101        Struct(Struct),
102        /// An enum ADT variant.
103        Enum(Enum),
104    }
105
106    /// Represents visibility modifiers for items.
107    pub enum Vis {
108        /// `pub(in? crate::foo::bar)`/`pub(in? ::foo::bar)`
109        PubIn(Cons<KPub, ParenthesisGroupContaining<Cons<Option<KIn>, ModPath>>>),
110        /// Public visibility, indicated by the "pub" keyword.
111        Pub(KPub),
112    }
113
114    /// Represents an attribute annotation on a field, typically in the form `#[attr]`.
115    pub struct Attribute {
116        /// The pound sign preceding the attribute.
117        pub _pound: Pound,
118        /// The content of the attribute enclosed in square brackets.
119        pub body: BracketGroupContaining<AttributeInner>,
120    }
121
122    /// Represents the inner content of an attribute annotation.
123    pub enum AttributeInner {
124        /// A facet attribute that can contain specialized metadata.
125        Facet(FacetAttr),
126        /// A documentation attribute typically used for generating documentation.
127        Doc(DocInner),
128        /// A representation attribute that specifies how data should be laid out.
129        Repr(ReprInner),
130        /// Any other attribute represented as a sequence of token trees.
131        Any(Vec<TokenTree>),
132    }
133
134    /// Represents a facet attribute that can contain specialized metadata.
135    pub struct FacetAttr {
136        /// The keyword for the facet attribute.
137        pub _facet: KFacet,
138        /// The inner content of the facet attribute.
139        pub inner: ParenthesisGroupContaining<CommaDelimitedVec<FacetInner>>,
140    }
141
142    /// Represents the inner content of a facet attribute.
143    pub enum FacetInner {
144        /// A sensitive attribute that specifies sensitivity information.
145        Sensitive(KSensitive),
146        /// An invariants attribute that specifies invariants for the type.
147        Invariants(InvariantInner),
148        /// An opaque attribute that specifies opaque information.
149        Opaque(KOpaque),
150        /// A deny_unknown_fields attribute that specifies whether unknown fields are allowed.
151        DenyUnknownFields(KDenyUnknownFields),
152        /// A default attribute with an explicit value (#[facet(default = "myfunc")])
153        DefaultEquals(DefaultEqualsInner),
154        /// A default attribute with no explicit value (#[facet(default)])
155        Default(KDefault),
156        /// A transparent attribute for containers
157        Transparent(KTransparent),
158        /// A rename_all attribute that specifies a case conversion for all fields/variants (#[facet(rename_all = "camelCase")])
159        RenameAll(RenameAllInner),
160        /// A rename attribute that specifies a custom name for a field/variant (#[facet(rename = "custom_name")])
161        Rename(RenameInner),
162        /// A flatten attribute that marks a field to be flattened into the parent structure
163        Flatten(FlattenInner),
164        /// A child attribute that marks a field as a child node
165        Child(ChildInner),
166        /// A skip_serializing attribute that specifies whether a field should be skipped during serialization.
167        SkipSerializing(SkipSerializingInner),
168        /// A skip_serializing_if attribute that specifies a condition for skipping serialization.
169        SkipSerializingIf(SkipSerializingIfInner),
170        /// A type_tag attribute that specifies the identifying tag for self describing formats
171        TypeTag(TypeTagInner),
172        /// A function to define how to deserializize the target
173        DeserializeWith(DeserializeWithInner),
174        /// A function to define how to serializize the target
175        SerializeWith(SerializeWithInner),
176        /// Any other attribute represented as a sequence of token trees.
177        Arbitrary(VerbatimUntil<Comma>),
178    }
179
180    /// Inner value for #[facet(flatten)]
181    pub struct FlattenInner {
182        /// The "flatten" keyword.
183        pub _kw_flatten: KFlatten,
184    }
185
186    /// Inner value for #[facet(child)]
187    pub struct ChildInner {
188        /// The "child" keyword.
189        pub _kw_child: KChild,
190    }
191
192    /// Inner value for #[facet(skip_serializing)]
193    pub struct SkipSerializingInner {
194        /// The "skip_serializing" keyword.
195        pub _kw_skip_serializing: KSkipSerializing,
196    }
197
198    /// Inner value for #[facet(skip_serializing_if = ...)]
199    pub struct SkipSerializingIfInner {
200        /// The "skip_serializing_if" keyword.
201        pub _kw_skip_serializing_if: KSkipSerializingIf,
202        /// The equals sign '='.
203        pub _eq: Eq,
204        /// The conditional expression as verbatim until comma.
205        pub expr: VerbatimUntil<Comma>,
206    }
207
208    /// Inner value for #[facet(type_tag = ...)]
209    pub struct TypeTagInner {
210        /// The "type_tag" keyword.
211        pub _kw_type_tag: KTypeTag,
212        /// The equals sign '='.
213        pub _eq: Eq,
214        /// The value assigned, as a literal string.
215        pub expr: LiteralString,
216    }
217
218    /// Inner value for #[facet(deserialize_with = ...)]
219    pub struct DeserializeWithInner {
220        /// The "deserialize_with" keyword.
221        pub _kw_deserialize_with: KDeserializeWith,
222        /// The equals sign '='.
223        pub _eq: Eq,
224        /// The conditional expression as verbatim until comma.
225        pub expr: VerbatimUntil<Comma>,
226    }
227
228    /// Inner value for #[facet(serialize_with = ...)]
229    pub struct SerializeWithInner {
230        /// The "serialize_with" keyword.
231        pub _kw_serialize_with: KSerializeWith,
232        /// The equals sign '='.
233        pub _eq: Eq,
234        /// The conditional expression as verbatim until comma.
235        pub expr: VerbatimUntil<Comma>,
236    }
237
238    /// Inner value for #[facet(default = ...)]
239    pub struct DefaultEqualsInner {
240        /// The "default" keyword.
241        pub _kw_default: KDefault,
242        /// The equals sign '='.
243        pub _eq: Eq,
244        /// The value assigned, as verbatim until comma.
245        pub expr: VerbatimUntil<Comma>,
246    }
247
248    /// Inner value for #[facet(rename = ...)]
249    pub struct RenameInner {
250        /// The "rename" keyword.
251        pub _kw_rename: KRename,
252        /// The equals sign '='.
253        pub _eq: Eq,
254        /// The value assigned, as a literal string.
255        pub value: LiteralString,
256    }
257
258    /// Inner value for #[facet(rename_all = ...)]
259    pub struct RenameAllInner {
260        /// The "rename_all" keyword.
261        pub _kw_rename_all: KRenameAll,
262        /// The equals sign '='.
263        pub _eq: Eq,
264        /// The value assigned, as a literal string.
265        pub value: LiteralString,
266    }
267
268    /// Represents invariants for a type.
269    pub struct InvariantInner {
270        /// The "invariants" keyword.
271        pub _kw_invariants: KInvariants,
272        /// The equality operator.
273        pub _eq: Eq,
274        /// The invariant value
275        pub expr: VerbatimUntil<Comma>,
276    }
277
278    /// Represents documentation for an item.
279    pub struct DocInner {
280        /// The "doc" keyword.
281        pub _kw_doc: KDoc,
282        /// The equality operator.
283        pub _eq: Eq,
284        /// The documentation content as a literal string.
285        pub value: LiteralString,
286    }
287
288    /// Represents the inner content of a `repr` attribute, typically used for specifying
289    /// memory layout or representation hints.
290    pub struct ReprInner {
291        /// The "repr" keyword.
292        pub _kw_repr: KRepr,
293        /// The representation attributes enclosed in parentheses.
294        pub attr: ParenthesisGroupContaining<CommaDelimitedVec<Ident>>,
295    }
296
297    /// Represents a struct definition.
298    pub struct Struct {
299        /// Attributes applied to the struct.
300        pub attributes: Vec<Attribute>,
301        /// The visibility modifier of the struct (e.g., `pub`).
302        pub _vis: Option<Vis>,
303        /// The "struct" keyword.
304        pub _kw_struct: KStruct,
305        /// The name of the struct.
306        pub name: Ident,
307        /// Generic parameters for the struct, if any.
308        pub generics: Option<GenericParams>,
309        /// The variant of struct (unit, tuple, or regular struct with named fields).
310        pub kind: StructKind,
311    }
312
313    /// Represents the generic parameters of a struct or enum definition, enclosed in angle brackets.
314    /// e.g., `<'a, T: Trait, const N: usize>`.
315    pub struct GenericParams {
316        /// The opening angle bracket `<`.
317        pub _lt: Lt,
318        /// The comma-delimited list of generic parameters.
319        pub params: CommaDelimitedVec<GenericParam>,
320        /// The closing angle bracket `>`.
321        pub _gt: Gt,
322    }
323
324    /// Represents a single generic parameter within a `GenericParams` list.
325    pub enum GenericParam {
326        /// A lifetime parameter, e.g., `'a` or `'a: 'b + 'c`.
327        Lifetime {
328            /// The lifetime identifier (e.g., `'a`).
329            name: Lifetime,
330            /// Optional lifetime bounds (e.g., `: 'b + 'c`).
331            bounds: Option<Cons<Colon, VerbatimUntil<Either<Comma, Gt>>>>,
332        },
333        /// A const generic parameter, e.g., `const N: usize = 10`.
334        Const {
335            /// The `const` keyword.
336            _const: KConst,
337            /// The name of the const parameter (e.g., `N`).
338            name: Ident,
339            /// The colon separating the name and type.
340            _colon: Colon,
341            /// The type of the const parameter (e.g., `usize`).
342            typ: VerbatimUntil<Either<Comma, Gt, Eq>>,
343            /// An optional default value (e.g., `= 10`).
344            default: Option<Cons<Eq, VerbatimUntil<Either<Comma, Gt>>>>,
345        },
346        /// A type parameter, e.g., `T: Trait = DefaultType`.
347        Type {
348            /// The name of the type parameter (e.g., `T`).
349            name: Ident,
350            /// Optional type bounds (e.g., `: Trait`).
351            bounds: Option<Bounds>,
352            /// An optional default type (e.g., `= DefaultType`).
353            default: Option<Cons<Eq, VerbatimUntil<Either<Comma, Gt>>>>,
354        },
355    }
356
357    /// Represents a `where` clause attached to a definition.
358    /// e.g., `where T: Trait, 'a: 'b`.
359    #[derive(Clone)]
360    pub struct WhereClauses {
361        /// The `where` keyword.
362        pub _kw_where: KWhere,
363        /// The comma-delimited list of where clause predicates.
364        pub clauses: CommaDelimitedVec<WhereClause>,
365    }
366
367    /// Represents a single predicate within a `where` clause.
368    /// e.g., `T: Trait` or `'a: 'b`.
369    #[derive(Clone)]
370    pub struct WhereClause {
371        /// The type or lifetime being constrained (e.g., `T` or `'a`).
372        /// We specifically required a single colon, not 2 because of `<A as B>::Thingy`
373        pub _pred: VerbatimUntil<Cons<Colon, Except<Colon>>>,
374        /// The colon separating the constrained item and its bounds.
375        pub _colon: Colon,
376        /// The bounds applied to the type or lifetime (e.g., `Trait` or `'b`).
377        pub bounds: VerbatimUntil<Either<Comma, Semicolon, BraceGroup>>,
378    }
379
380    /// Represents the kind of a struct definition.
381    pub enum StructKind {
382        /// A regular struct with named fields, e.g., `struct Foo { bar: u32 }`.
383        Struct {
384            /// Optional where clauses.
385            clauses: Option<WhereClauses>,
386            /// The fields enclosed in braces `{}`.
387            fields: BraceGroupContaining<CommaDelimitedVec<StructField>>,
388        },
389        /// A tuple struct, e.g., `struct Foo(u32, String);`.
390        TupleStruct {
391            /// The fields enclosed in parentheses `()`.
392            fields: ParenthesisGroupContaining<CommaDelimitedVec<TupleField>>,
393            /// Optional where clauses.
394            clauses: Option<WhereClauses>,
395            /// The trailing semicolon `;`.
396            semi: Semi,
397        },
398        /// A unit struct, e.g., `struct Foo;`.
399        UnitStruct {
400            /// Optional where clauses.
401            clauses: Option<WhereClauses>,
402            /// The trailing semicolon `;`.
403            semi: Semi,
404        },
405    }
406
407    /// Represents a lifetime annotation, like `'a`.
408    pub struct Lifetime {
409        /// The apostrophe `'` starting the lifetime.
410        pub _apostrophe: PunctJoint<'\''>,
411        /// The identifier name of the lifetime (e.g., `a`).
412        pub name: Ident,
413    }
414
415    /// Represents a simple expression, currently only integer literals.
416    /// Used potentially for const generic default values.
417    pub enum Expr {
418        /// An integer literal expression.
419        Integer(LiteralInteger),
420    }
421
422    /// Represents either the `const` or `mut` keyword, often used with pointers.
423    pub enum ConstOrMut {
424        /// The `const` keyword.
425        Const(KConst),
426        /// The `mut` keyword.
427        Mut(KMut),
428    }
429
430    /// Represents a field within a regular struct definition.
431    /// e.g., `pub name: String`.
432    pub struct StructField {
433        /// Attributes applied to the field (e.g., `#[doc = "..."]`).
434        pub attributes: Vec<Attribute>,
435        /// Optional visibility modifier (e.g., `pub`).
436        pub _vis: Option<Vis>,
437        /// The name of the field.
438        pub name: Ident,
439        /// The colon separating the name and type.
440        pub _colon: Colon,
441        /// The type of the field.
442        pub typ: VerbatimUntil<Comma>,
443    }
444
445    /// Represents a field within a tuple struct definition.
446    /// e.g., `pub String`.
447    pub struct TupleField {
448        /// Attributes applied to the field (e.g., `#[doc = "..."]`).
449        pub attributes: Vec<Attribute>,
450        /// Optional visibility modifier (e.g., `pub`).
451        pub vis: Option<Vis>,
452        /// The type of the field.
453        pub typ: VerbatimUntil<Comma>,
454    }
455
456    /// Represents an enum definition.
457    /// e.g., `#[repr(u8)] pub enum MyEnum<T> where T: Clone { Variant1, Variant2(T) }`.
458    pub struct Enum {
459        /// Attributes applied to the enum (e.g., `#[repr(...)]`).
460        pub attributes: Vec<Attribute>,
461        /// Optional visibility modifier (e.g., `pub`, `pub(crate)`, etc.).
462        pub _vis: Option<Vis>,
463        /// The `enum` keyword.
464        pub _kw_enum: KEnum,
465        /// The name of the enum.
466        pub name: Ident,
467        /// Optional generic parameters.
468        pub generics: Option<GenericParams>,
469        /// Optional where clauses.
470        pub clauses: Option<WhereClauses>,
471        /// The enum variants enclosed in braces `{}`.
472        pub body: BraceGroupContaining<CommaDelimitedVec<EnumVariantLike>>,
473    }
474
475    /// Represents a variant of an enum, including the optional discriminant value
476    pub struct EnumVariantLike {
477        /// The actual variant
478        pub variant: EnumVariantData,
479        /// The optional discriminant value
480        pub discriminant: Option<Cons<Eq, VerbatimUntil<Comma>>>
481    }
482
483    /// Represents the different kinds of variants an enum can have.
484    pub enum EnumVariantData {
485        /// A tuple-like variant, e.g., `Variant(u32, String)`.
486        Tuple(TupleVariant),
487        /// A struct-like variant, e.g., `Variant { field1: u32, field2: String }`.
488        Struct(StructEnumVariant),
489        /// A unit-like variant, e.g., `Variant`.
490        Unit(UnitVariant),
491    }
492
493    /// Represents a unit-like enum variant.
494    /// e.g., `MyVariant`.
495    pub struct UnitVariant {
496        /// Attributes applied to the variant.
497        pub attributes: Vec<Attribute>,
498        /// The name of the variant.
499        pub name: Ident,
500    }
501
502    /// Represents a tuple-like enum variant.
503    /// e.g., `MyVariant(u32, String)`.
504    pub struct TupleVariant {
505        /// Attributes applied to the variant.
506        pub attributes: Vec<Attribute>,
507        /// The name of the variant.
508        pub name: Ident,
509        /// The fields enclosed in parentheses `()`.
510        pub fields: ParenthesisGroupContaining<CommaDelimitedVec<TupleField>>,
511    }
512
513    /// Represents a struct-like enum variant.
514    /// e.g., `MyVariant { field1: u32, field2: String }`.
515    pub struct StructEnumVariant {
516        /// Attributes applied to the variant.
517        pub attributes: Vec<Attribute>,
518        /// The name of the variant.
519        pub name: Ident,
520        /// The fields enclosed in braces `{}`.
521        pub fields: BraceGroupContaining<CommaDelimitedVec<StructField>>,
522    }
523
524    /// A lifetime or a tokentree, used to gather lifetimes in type definitions
525    pub enum LifetimeOrTt {
526        /// A lifetime annotation.
527        Lifetime(Lifetime),
528        /// A single token tree.
529        TokenTree(TokenTree),
530    }
531}
532
533impl core::fmt::Display for AngleTokenTree {
534    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
535        match &self.0 {
536            Either::First(it) => {
537                write!(f, "<")?;
538                for it in it.second.iter() {
539                    write!(f, "{}", it.second)?;
540                }
541                write!(f, ">")?;
542            }
543            Either::Second(it) => write!(f, "{it}")?,
544            Either::Third(Invalid) => unreachable!(),
545            Either::Fourth(Invalid) => unreachable!(),
546        };
547        Ok(())
548    }
549}
550
551/// Display the verbatim tokens until the given token.
552pub struct VerbatimDisplay<'a, C>(pub &'a VerbatimUntil<C>);
553
554impl<C> core::fmt::Display for VerbatimDisplay<'_, C> {
555    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
556        for tt in self.0.iter() {
557            write!(f, "{}", tt.value.second)?;
558        }
559        Ok(())
560    }
561}
562
563impl core::fmt::Display for ConstOrMut {
564    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
565        match self {
566            ConstOrMut::Const(_) => write!(f, "const"),
567            ConstOrMut::Mut(_) => write!(f, "mut"),
568        }
569    }
570}
571
572impl core::fmt::Display for Lifetime {
573    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
574        write!(f, "'{}", self.name)
575    }
576}
577
578impl core::fmt::Display for WhereClauses {
579    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580        write!(f, "where ")?;
581        for clause in self.clauses.iter() {
582            write!(f, "{},", clause.value)?;
583        }
584        Ok(())
585    }
586}
587
588impl core::fmt::Display for WhereClause {
589    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
590        write!(
591            f,
592            "{}: {}",
593            VerbatimDisplay(&self._pred),
594            VerbatimDisplay(&self.bounds)
595        )
596    }
597}
598
599impl core::fmt::Display for Expr {
600    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
601        match self {
602            Expr::Integer(int) => write!(f, "{}", int.value()),
603        }
604    }
605}
606
607impl Struct {
608    /// Returns an iterator over the `FacetInner` content of `#[facet(...)]` attributes
609    /// applied to this struct.
610    pub fn facet_attributes(&self) -> impl Iterator<Item = &FacetInner> {
611        self.attributes
612            .iter()
613            .filter_map(|attr| match &attr.body.content {
614                AttributeInner::Facet(f) => Some(f.inner.content.as_slice()),
615                _ => None,
616            })
617            .flatten()
618            .map(|d| &d.value)
619    }
620
621    /// Returns `true` if the struct is marked `#[facet(transparent)]`.
622    pub fn is_transparent(&self) -> bool {
623        self.facet_attributes()
624            .any(|inner| matches!(inner, FacetInner::Transparent(_)))
625    }
626}
627
628#[cfg(test)]
629mod tests {
630    use super::*;
631    use quote::quote;
632
633    #[test]
634    fn test_struct_with_field_doc_comments() {
635        let input = quote! {
636            #[derive(Facet)]
637            pub struct User {
638                #[doc = " The user's unique identifier"]
639                pub id: u64,
640            }
641        };
642
643        let mut it = input.to_token_iter();
644        let parsed = it.parse::<Struct>().expect("Failed to parse struct");
645
646        // Check that we parsed the struct correctly
647        assert_eq!(parsed.name.to_string(), "User");
648
649        // Extract fields from the struct
650        if let StructKind::Struct { fields, .. } = &parsed.kind {
651            assert_eq!(fields.content.len(), 1);
652
653            // Check first field (id)
654            let id_field = &fields.content[0].value;
655            assert_eq!(id_field.name.to_string(), "id");
656
657            // Extract doc comments from id field
658            let mut doc_found = false;
659            for attr in &id_field.attributes {
660                match &attr.body.content {
661                    AttributeInner::Doc(doc_inner) => {
662                        // This should work with LiteralString
663                        assert_eq!(doc_inner.value, " The user's unique identifier");
664                        doc_found = true;
665                    }
666                    _ => {
667                        // Skip non-doc attributes
668                    }
669                }
670            }
671            assert!(doc_found, "Should have found a doc comment");
672        } else {
673            panic!("Expected a regular struct with named fields");
674        }
675    }
676}