Skip to main content

facet_macro_parse/
parsed.rs

1use crate::{BoundedGenericParams, RenameRule, unescape};
2use crate::{Ident, KWhere, ReprInner, ToTokens, TokenStream};
3use proc_macro2::Span;
4use quote::{quote, quote_spanned};
5
6/// Errors that can occur during parsing of derive macro attributes.
7///
8/// Some errors are caught by rustc itself, so we don't emit duplicate diagnostics.
9/// Others are facet-specific and need our own compile_error!.
10#[derive(Debug)]
11pub enum ParseError {
12    /// An error that rustc will catch on its own - we don't emit a diagnostic.
13    ///
14    /// We track these so the code is explicit about why we're not panicking,
15    /// and to document what rustc catches.
16    RustcWillCatch {
17        /// Description of what rustc will catch (for documentation purposes)
18        reason: &'static str,
19    },
20
21    /// A facet-specific error that rustc won't catch - we emit compile_error!
22    FacetError {
23        /// The error message to display
24        message: String,
25        /// The span to point the error at
26        span: Span,
27    },
28}
29
30impl ParseError {
31    /// Create a "rustc will catch this" error.
32    ///
33    /// Use this when we detect an error that rustc will also catch,
34    /// so we avoid duplicate diagnostics.
35    pub const fn rustc_will_catch(reason: &'static str) -> Self {
36        ParseError::RustcWillCatch { reason }
37    }
38
39    /// Create a facet-specific error with a span.
40    pub fn facet_error(message: impl Into<String>, span: Span) -> Self {
41        ParseError::FacetError {
42            message: message.into(),
43            span,
44        }
45    }
46
47    /// Convert to a compile_error! TokenStream, or None if rustc will catch it.
48    pub fn to_compile_error(&self) -> Option<TokenStream> {
49        match self {
50            ParseError::RustcWillCatch { .. } => None,
51            ParseError::FacetError { message, span } => {
52                Some(quote_spanned! { *span => compile_error!(#message); })
53            }
54        }
55    }
56}
57
58/// For struct fields, they can either be identifiers (`my_struct.foo`)
59/// or literals (`my_struct.2`) — for tuple structs.
60#[derive(Clone)]
61pub enum IdentOrLiteral {
62    /// Named field identifier
63    Ident(Ident),
64    /// Tuple field index
65    Literal(usize),
66}
67
68impl quote::ToTokens for IdentOrLiteral {
69    fn to_tokens(&self, tokens: &mut TokenStream) {
70        match self {
71            IdentOrLiteral::Ident(ident) => tokens.extend(quote::quote! { #ident }),
72            IdentOrLiteral::Literal(lit) => {
73                let unsuffixed = crate::Literal::usize_unsuffixed(*lit);
74                tokens.extend(quote! { #unsuffixed })
75            }
76        }
77    }
78}
79
80/// The key of a facet attribute - either an identifier or the `where` keyword.
81#[derive(Clone)]
82pub enum AttrKey {
83    /// A regular identifier (e.g., "sensitive", "rename", "opaque")
84    Ident(Ident),
85    /// The `where` keyword for custom bounds
86    Where(KWhere),
87}
88
89impl AttrKey {
90    /// Returns the span of the key
91    pub fn span(&self) -> Span {
92        match self {
93            AttrKey::Ident(ident) => ident.span(),
94            AttrKey::Where(kw) => kw.span(),
95        }
96    }
97}
98
99impl PartialEq<&str> for AttrKey {
100    fn eq(&self, other: &&str) -> bool {
101        match self {
102            AttrKey::Ident(ident) => ident == other,
103            AttrKey::Where(_) => *other == "where",
104        }
105    }
106}
107
108impl PartialEq<String> for AttrKey {
109    fn eq(&self, other: &String) -> bool {
110        match self {
111            AttrKey::Ident(ident) => ident == other.as_str(),
112            AttrKey::Where(_) => other == "where",
113        }
114    }
115}
116
117impl ToTokens for AttrKey {
118    fn to_tokens(&self, tokens: &mut TokenStream) {
119        match self {
120            AttrKey::Ident(ident) => ident.to_tokens(tokens),
121            AttrKey::Where(kw) => kw.to_tokens(tokens),
122        }
123    }
124}
125
126impl quote::ToTokens for AttrKey {
127    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
128        match self {
129            AttrKey::Ident(ident) => quote::ToTokens::to_tokens(ident, tokens),
130            AttrKey::Where(kw) => {
131                // Convert unsynn TokenStream to proc_macro2 TokenStream
132                let ts: TokenStream = ToTokens::to_token_stream(kw);
133                tokens.extend(ts);
134            }
135        }
136    }
137}
138
139/// A parsed facet attribute.
140///
141/// All attributes are now stored uniformly - either with a namespace (`xml::element`)
142/// or without (`sensitive`). The grammar system handles validation and semantics.
143#[derive(Clone)]
144pub struct PFacetAttr {
145    /// The namespace (e.g., "xml", "args"). None for builtin attributes.
146    pub ns: Option<Ident>,
147    /// The key (e.g., "element", "sensitive", "rename", or `where`)
148    pub key: AttrKey,
149    /// The arguments as a TokenStream
150    pub args: TokenStream,
151}
152
153impl PFacetAttr {
154    /// Parse a `FacetAttr` attribute into `PFacetAttr` entries.
155    ///
156    /// All attributes are captured uniformly as ns/key/args.
157    /// The grammar system handles validation - we just capture the tokens.
158    pub fn parse(facet_attr: &crate::FacetAttr, dest: &mut Vec<PFacetAttr>) {
159        use crate::{AttrArgs, FacetInner, ToTokens};
160
161        for attr in facet_attr.inner.content.iter().map(|d| &d.value) {
162            match attr {
163                // Namespaced attributes like `xml::element` or `xml::ns = "http://example.com"`
164                FacetInner::Namespaced(ext) => {
165                    let args = match &ext.args {
166                        Some(AttrArgs::Parens(p)) => p.content.to_token_stream(),
167                        Some(AttrArgs::Equals(e)) => e.value.to_token_stream(),
168                        None => TokenStream::new(),
169                    };
170                    dest.push(PFacetAttr {
171                        ns: Some(ext.ns.clone()),
172                        key: AttrKey::Ident(ext.key.clone()),
173                        args,
174                    });
175                }
176
177                // Where clause attributes like `where T: Clone`
178                FacetInner::Where(where_attr) => {
179                    dest.push(PFacetAttr {
180                        ns: None,
181                        key: AttrKey::Where(where_attr._kw_where.clone()),
182                        args: where_attr.bounds.to_token_stream(),
183                    });
184                }
185
186                // Simple (builtin) attributes like `sensitive` or `rename = "foo"`
187                FacetInner::Simple(simple) => {
188                    let args = match &simple.args {
189                        Some(AttrArgs::Parens(p)) => p.content.to_token_stream(),
190                        Some(AttrArgs::Equals(e)) => e.value.to_token_stream(),
191                        None => TokenStream::new(),
192                    };
193                    dest.push(PFacetAttr {
194                        ns: None,
195                        key: AttrKey::Ident(simple.key.clone()),
196                        args,
197                    });
198                }
199            }
200        }
201    }
202
203    /// Returns true if this is a builtin attribute (no namespace)
204    pub const fn is_builtin(&self) -> bool {
205        self.ns.is_none()
206    }
207
208    /// Returns the key as a string
209    pub fn key_str(&self) -> String {
210        match &self.key {
211            AttrKey::Ident(ident) => ident.to_string(),
212            AttrKey::Where(w) => w.as_str().to_string(),
213        }
214    }
215}
216
217/// Parsed attr
218pub enum PAttr {
219    /// A single line of doc comments
220    /// `#[doc = "Some doc"], or `/// Some doc`, same thing
221    Doc {
222        /// The doc comment text
223        line: String,
224    },
225
226    /// A representation attribute
227    Repr {
228        /// The parsed repr
229        repr: PRepr,
230    },
231
232    /// A facet attribute
233    Facet {
234        /// The facet attribute name
235        name: String,
236    },
237}
238
239/// A parsed name, which includes the raw name and the
240/// effective name.
241///
242/// Examples:
243///
244///   raw = "foo_bar", no rename rule, effective = "foo_bar"
245///   raw = "foo_bar", #[facet(rename = "kiki")], effective = "kiki"
246///   raw = "foo_bar", #[facet(rename_all = camelCase)], effective = "fooBar"
247///   raw = "r#type", no rename rule, effective = "type"
248///
249#[derive(Clone)]
250pub struct PName {
251    /// The raw identifier, as we found it in the source code. It might
252    /// be _actually_ raw, as in `r#keyword`.
253    pub raw: IdentOrLiteral,
254
255    /// If raw was `r#keyword`, then this one is, naturally, just `keyword`.
256    pub original: String,
257
258    /// This is `original` after applying rename rules, which might not be a valid identifier in
259    /// Rust. It could be a number. It could be a kebab-case thing. It could be anything!
260    ///
261    /// If it's `None`, there was no `#[facet(rename)]` attr on the field, or `#[facet(rename_all)]`
262    /// on the parent container, and it would've been the same as `original` anyway.
263    pub rename: Option<String>,
264}
265
266impl PName {
267    /// Constructs a new `PName`.
268    ///
269    /// Precedence for `rename`:
270    ///   1. Field-level `rename` if provided
271    ///   2. Container-level `rename_rule` applied to `original`
272    ///   3. None
273    pub fn new(
274        raw: IdentOrLiteral,
275        container_rename_rule: Option<RenameRule>,
276        rename: Option<String>,
277    ) -> Self {
278        let original = match &raw {
279            IdentOrLiteral::Ident(ident) => ident
280                .tokens_to_string()
281                .trim_start_matches("r#")
282                .to_string(),
283            IdentOrLiteral::Literal(l) => l.to_string(),
284        };
285
286        let rename = rename.or_else(|| container_rename_rule.map(|rule| rule.apply(&original)));
287
288        Self {
289            raw,
290            original,
291            rename,
292        }
293    }
294}
295
296/// Parsed representation attribute
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
298pub enum PRepr {
299    /// `#[repr(transparent)]`
300    Transparent,
301    /// `#[repr(Rust)]` with optional primitive type
302    Rust(Option<PrimitiveRepr>),
303    /// `#[repr(C)]` with optional primitive type
304    C(Option<PrimitiveRepr>),
305    /// A repr error that rustc will catch (e.g., conflicting hints).
306    /// We use this sentinel to avoid emitting our own misleading errors.
307    RustcWillCatch,
308}
309
310impl PRepr {
311    /// Parse a `&str` (for example a value coming from #[repr(...)] attribute)
312    /// into a `PRepr` variant.
313    ///
314    /// Returns `Err(ParseError::RustcWillCatch { .. })` for errors that rustc
315    /// will catch on its own (conflicting repr hints). Returns
316    /// `Err(ParseError::FacetError { .. })` for facet-specific errors like
317    /// unsupported repr types (e.g., `packed`).
318    pub fn parse(s: &ReprInner) -> Result<Option<Self>, ParseError> {
319        enum ReprKind {
320            Rust,
321            C,
322        }
323
324        let items = s.attr.content.as_slice();
325        let mut repr_kind: Option<ReprKind> = None;
326        let mut primitive_repr: Option<PrimitiveRepr> = None;
327        let mut is_transparent = false;
328
329        for token_delimited in items {
330            let token_str = token_delimited.value.to_string();
331            let token_span = token_delimited.value.span();
332
333            match token_str.as_str() {
334                "C" | "c" => {
335                    if repr_kind.is_some() && !matches!(repr_kind, Some(ReprKind::C)) {
336                        // rustc emits E0566: conflicting representation hints
337                        return Err(ParseError::rustc_will_catch(
338                            "E0566: conflicting representation hints (C vs Rust)",
339                        ));
340                    }
341                    if is_transparent {
342                        // rustc emits E0692: transparent struct/enum cannot have other repr hints
343                        return Err(ParseError::rustc_will_catch(
344                            "E0692: transparent cannot have other repr hints",
345                        ));
346                    }
347                    repr_kind = Some(ReprKind::C);
348                }
349                "Rust" | "rust" => {
350                    if repr_kind.is_some() && !matches!(repr_kind, Some(ReprKind::Rust)) {
351                        // rustc emits E0566: conflicting representation hints
352                        return Err(ParseError::rustc_will_catch(
353                            "E0566: conflicting representation hints (Rust vs C)",
354                        ));
355                    }
356                    if is_transparent {
357                        // rustc emits E0692: transparent struct/enum cannot have other repr hints
358                        return Err(ParseError::rustc_will_catch(
359                            "E0692: transparent cannot have other repr hints",
360                        ));
361                    }
362                    repr_kind = Some(ReprKind::Rust);
363                }
364                "transparent" => {
365                    if repr_kind.is_some() || primitive_repr.is_some() {
366                        // rustc emits E0692: transparent struct/enum cannot have other repr hints
367                        return Err(ParseError::rustc_will_catch(
368                            "E0692: transparent cannot have other repr hints",
369                        ));
370                    }
371                    is_transparent = true;
372                }
373                prim_str @ ("u8" | "u16" | "u32" | "u64" | "u128" | "i8" | "i16" | "i32"
374                | "i64" | "i128" | "usize" | "isize") => {
375                    let current_prim = match prim_str {
376                        "u8" => PrimitiveRepr::U8,
377                        "u16" => PrimitiveRepr::U16,
378                        "u32" => PrimitiveRepr::U32,
379                        "u64" => PrimitiveRepr::U64,
380                        "u128" => PrimitiveRepr::U128,
381                        "i8" => PrimitiveRepr::I8,
382                        "i16" => PrimitiveRepr::I16,
383                        "i32" => PrimitiveRepr::I32,
384                        "i64" => PrimitiveRepr::I64,
385                        "i128" => PrimitiveRepr::I128,
386                        "usize" => PrimitiveRepr::Usize,
387                        "isize" => PrimitiveRepr::Isize,
388                        _ => unreachable!(),
389                    };
390                    if is_transparent {
391                        // rustc emits E0692: transparent struct/enum cannot have other repr hints
392                        return Err(ParseError::rustc_will_catch(
393                            "E0692: transparent cannot have other repr hints",
394                        ));
395                    }
396                    if primitive_repr.is_some() {
397                        // rustc emits E0566: conflicting representation hints
398                        return Err(ParseError::rustc_will_catch(
399                            "E0566: conflicting representation hints (multiple primitives)",
400                        ));
401                    }
402                    primitive_repr = Some(current_prim);
403                }
404                unknown => {
405                    // This is a facet-specific error: rustc accepts things like `packed`,
406                    // `align(N)`, etc., but facet doesn't support them.
407                    return Err(ParseError::facet_error(
408                        format!(
409                            "unsupported repr `{unknown}` - facet only supports \
410                             C, Rust, transparent, and primitive integer types"
411                        ),
412                        token_span,
413                    ));
414                }
415            }
416        }
417
418        // Final construction
419        if is_transparent {
420            debug_assert!(
421                repr_kind.is_none() && primitive_repr.is_none(),
422                "internal error: transparent repr mixed with other kinds after parsing"
423            );
424            Ok(Some(PRepr::Transparent))
425        } else {
426            let final_kind = repr_kind.unwrap_or(ReprKind::Rust);
427            Ok(Some(match final_kind {
428                ReprKind::Rust => PRepr::Rust(primitive_repr),
429                ReprKind::C => PRepr::C(primitive_repr),
430            }))
431        }
432    }
433}
434
435/// Primitive repr types
436#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
437pub enum PrimitiveRepr {
438    /// `u8`
439    U8,
440    /// `u16`
441    U16,
442    /// `u32`
443    U32,
444    /// `u64`
445    U64,
446    /// `u128`
447    U128,
448    /// `i8`
449    I8,
450    /// `i16`
451    I16,
452    /// `i32`
453    I32,
454    /// `i64`
455    I64,
456    /// `i128`
457    I128,
458    /// `isize`
459    Isize,
460    /// `usize`
461    Usize,
462}
463
464impl PrimitiveRepr {
465    /// Returns the type name as a token stream
466    pub fn type_name(&self) -> TokenStream {
467        match self {
468            PrimitiveRepr::U8 => quote! { u8 },
469            PrimitiveRepr::U16 => quote! { u16 },
470            PrimitiveRepr::U32 => quote! { u32 },
471            PrimitiveRepr::U64 => quote! { u64 },
472            PrimitiveRepr::U128 => quote! { u128 },
473            PrimitiveRepr::I8 => quote! { i8 },
474            PrimitiveRepr::I16 => quote! { i16 },
475            PrimitiveRepr::I32 => quote! { i32 },
476            PrimitiveRepr::I64 => quote! { i64 },
477            PrimitiveRepr::I128 => quote! { i128 },
478            PrimitiveRepr::Isize => quote! { isize },
479            PrimitiveRepr::Usize => quote! { usize },
480        }
481    }
482}
483
484/// A compile error to be emitted during code generation
485#[derive(Clone)]
486pub struct CompileError {
487    /// The error message
488    pub message: String,
489    /// The span where the error occurred
490    pub span: Span,
491}
492
493/// Tracks which traits are explicitly declared via `#[facet(traits(...))]`.
494///
495/// When this is present, we skip all `impls!` checks and only generate
496/// vtable entries for the declared traits.
497#[derive(Clone, Default)]
498pub struct DeclaredTraits {
499    /// Display trait declared
500    pub display: bool,
501    /// Debug trait declared
502    pub debug: bool,
503    /// Clone trait declared
504    pub clone: bool,
505    /// Copy trait declared (marker)
506    pub copy: bool,
507    /// PartialEq trait declared
508    pub partial_eq: bool,
509    /// Eq trait declared (marker)
510    pub eq: bool,
511    /// PartialOrd trait declared
512    pub partial_ord: bool,
513    /// Ord trait declared
514    pub ord: bool,
515    /// Hash trait declared
516    pub hash: bool,
517    /// Default trait declared
518    pub default: bool,
519    /// Send trait declared (marker)
520    pub send: bool,
521    /// Sync trait declared (marker)
522    pub sync: bool,
523    /// Unpin trait declared (marker)
524    pub unpin: bool,
525}
526
527impl DeclaredTraits {
528    /// Returns true if any trait is declared
529    pub const fn has_any(&self) -> bool {
530        self.display
531            || self.debug
532            || self.clone
533            || self.copy
534            || self.partial_eq
535            || self.eq
536            || self.partial_ord
537            || self.ord
538            || self.hash
539            || self.default
540            || self.send
541            || self.sync
542            || self.unpin
543    }
544
545    /// Parse traits from a token stream like `Debug, PartialEq, Clone, Send`
546    pub fn parse_from_tokens(tokens: &TokenStream, errors: &mut Vec<CompileError>) -> Self {
547        let mut result = DeclaredTraits::default();
548
549        for token in tokens.clone() {
550            if let proc_macro2::TokenTree::Ident(ident) = token {
551                let name = ident.to_string();
552                match name.as_str() {
553                    "Display" => result.display = true,
554                    "Debug" => result.debug = true,
555                    "Clone" => result.clone = true,
556                    "Copy" => result.copy = true,
557                    "PartialEq" => result.partial_eq = true,
558                    "Eq" => result.eq = true,
559                    "PartialOrd" => result.partial_ord = true,
560                    "Ord" => result.ord = true,
561                    "Hash" => result.hash = true,
562                    "Default" => result.default = true,
563                    "Send" => result.send = true,
564                    "Sync" => result.sync = true,
565                    "Unpin" => result.unpin = true,
566                    unknown => {
567                        errors.push(CompileError {
568                            message: format!(
569                                "unknown trait `{unknown}` in #[facet(traits(...))]. \
570                                 Valid traits: Display, Debug, Clone, Copy, PartialEq, Eq, \
571                                 PartialOrd, Ord, Hash, Default, Send, Sync, Unpin"
572                            ),
573                            span: ident.span(),
574                        });
575                    }
576                }
577            }
578        }
579
580        result
581    }
582}
583
584/// Parsed attributes
585#[derive(Clone)]
586pub struct PAttrs {
587    /// An array of doc lines
588    pub doc: Vec<String>,
589
590    /// Facet attributes specifically
591    pub facet: Vec<PFacetAttr>,
592
593    /// Representation of the facet
594    pub repr: PRepr,
595
596    /// rename_all rule (if any)
597    pub rename_all: Option<RenameRule>,
598
599    /// Field/variant-level rename (if any)
600    pub rename: Option<String>,
601
602    /// Custom crate path (if any), e.g., `::my_crate::facet`
603    pub crate_path: Option<TokenStream>,
604
605    /// Errors to be emitted as compile_error! during code generation
606    pub errors: Vec<CompileError>,
607
608    /// Explicitly declared traits via `#[facet(traits(...))]`
609    /// When present, we skip specialization-based detection and only generate
610    /// vtable entries for the declared traits.
611    pub declared_traits: Option<DeclaredTraits>,
612
613    /// Custom where clause bounds from `#[facet(bound = "...")]`
614    /// These are added to the generated Facet impl's where clause
615    pub custom_bounds: Vec<TokenStream>,
616}
617
618impl PAttrs {
619    /// Parse attributes from a list of `Attribute`s
620    pub fn parse(attrs: &[crate::Attribute]) -> Self {
621        let mut doc_lines: Vec<String> = Vec::new();
622        let mut facet_attrs: Vec<PFacetAttr> = Vec::new();
623        let mut repr: Option<PRepr> = None;
624        let mut rename_all: Option<RenameRule> = None;
625        let mut rename: Option<String> = None;
626        let mut crate_path: Option<TokenStream> = None;
627        let mut errors: Vec<CompileError> = Vec::new();
628
629        for attr in attrs {
630            match &attr.body.content {
631                crate::AttributeInner::Doc(doc_attr) => {
632                    let unescaped_text =
633                        unescape(doc_attr).expect("invalid escape sequence in doc string");
634                    doc_lines.push(unescaped_text);
635                }
636                crate::AttributeInner::Repr(repr_attr) => {
637                    if repr.is_some() {
638                        // rustc emits E0566: conflicting representation hints
639                        // for multiple #[repr] attributes - use sentinel
640                        repr = Some(PRepr::RustcWillCatch);
641                        continue;
642                    }
643
644                    match PRepr::parse(repr_attr) {
645                        Ok(Some(parsed)) => repr = Some(parsed),
646                        Ok(None) => { /* empty repr, use default */ }
647                        Err(ParseError::RustcWillCatch { .. }) => {
648                            // rustc will emit the error - use sentinel so we don't
649                            // emit misleading "missing repr" errors later
650                            repr = Some(PRepr::RustcWillCatch);
651                        }
652                        Err(ParseError::FacetError { message, span }) => {
653                            errors.push(CompileError { message, span });
654                        }
655                    }
656                }
657                crate::AttributeInner::Facet(facet_attr) => {
658                    PFacetAttr::parse(facet_attr, &mut facet_attrs);
659                }
660                // Note: Rust strips #[derive(...)] attributes before passing to derive macros,
661                // so we cannot detect them here. Users must use #[facet(traits(...))] instead.
662                crate::AttributeInner::Any(tokens) => {
663                    // WORKAROUND: Doc comments with raw string literals (r"...") are not
664                    // recognized by the DocInner parser, so they end up as Any attributes.
665                    // Parse them manually here: doc = <string literal>
666                    if tokens.len() == 3
667                        && let Some(proc_macro2::TokenTree::Ident(id)) = tokens.first()
668                        && id == "doc"
669                        && let Some(proc_macro2::TokenTree::Literal(lit)) = tokens.get(2)
670                    {
671                        // Extract the string value from the literal
672                        let lit_str = lit.to_string();
673                        // Handle both regular strings "..." and raw strings r"..."
674                        let content = if lit_str.starts_with("r#") {
675                            // Raw string with hashes: r#"..."#, r##"..."##, etc.
676                            let hash_count =
677                                lit_str.chars().skip(1).take_while(|&c| c == '#').count();
678                            let start = 2 + hash_count + 1; // r + hashes + "
679                            let end = lit_str.len() - 1 - hash_count; // " + hashes
680                            lit_str[start..end].to_string()
681                        } else if lit_str.starts_with('r') {
682                            // Simple raw string: r"..."
683                            let content = &lit_str[2..lit_str.len() - 1];
684                            content.to_string()
685                        } else {
686                            // Regular string: "..." - needs unescaping
687                            let trimmed = lit_str.trim_matches('"');
688                            match crate::unescape_inner(trimmed) {
689                                Ok(s) => s,
690                                Err(_) => continue, // Skip malformed strings
691                            }
692                        };
693                        doc_lines.push(content);
694                    }
695                }
696            }
697        }
698
699        // Extract rename, rename_all, crate, traits, and bound from parsed attrs
700        let mut declared_traits: Option<DeclaredTraits> = None;
701        let mut custom_bounds: Vec<TokenStream> = Vec::new();
702
703        for attr in &facet_attrs {
704            if attr.is_builtin() {
705                match &*attr.key_str() {
706                    "rename" => {
707                        let s = attr.args.to_string();
708                        let trimmed = s.trim().trim_matches('"');
709                        rename = Some(trimmed.to_string());
710                    }
711                    "rename_all" => {
712                        let s = attr.args.to_string();
713                        let rule_str = s.trim().trim_matches('"');
714                        if let Some(rule) = RenameRule::parse(rule_str) {
715                            rename_all = Some(rule);
716                        } else {
717                            errors.push(CompileError {
718                                message: format!(
719                                    "unknown #[facet(rename_all = \"...\")] rule: `{rule_str}`. \
720                                     Valid options: camelCase, snake_case, kebab-case, \
721                                     PascalCase, SCREAMING_SNAKE_CASE, SCREAMING-KEBAB-CASE, \
722                                     lowercase, UPPERCASE"
723                                ),
724                                span: attr.key.span(),
725                            });
726                        }
727                    }
728                    "crate" => {
729                        // Store the crate path tokens directly
730                        crate_path = Some(attr.args.clone());
731                    }
732                    "traits" => {
733                        // Parse #[facet(traits(Debug, PartialEq, Clone, ...))]
734                        declared_traits =
735                            Some(DeclaredTraits::parse_from_tokens(&attr.args, &mut errors));
736                    }
737                    "where" => {
738                        // #[facet(where T: Clone + Send)]
739                        // Parse the args directly as tokens (no string literal needed)
740                        let tokens = attr.args.clone();
741                        if !tokens.is_empty() {
742                            custom_bounds.push(tokens);
743                        } else {
744                            errors.push(CompileError {
745                                message: "expected where clause bounds, e.g., \
746                                          #[facet(where T: Clone)]"
747                                    .to_string(),
748                                span: attr.key.span(),
749                            });
750                        }
751                    }
752                    _ => {}
753                }
754            }
755        }
756
757        Self {
758            doc: doc_lines,
759            facet: facet_attrs,
760            repr: repr.unwrap_or(PRepr::Rust(None)),
761            rename_all,
762            rename,
763            crate_path,
764            errors,
765            declared_traits,
766            custom_bounds,
767        }
768    }
769
770    /// Check if a builtin attribute with the given key exists
771    pub fn has_builtin(&self, key: &str) -> bool {
772        self.facet
773            .iter()
774            .any(|a| a.is_builtin() && a.key_str() == key)
775    }
776
777    /// Check if `#[repr(transparent)]` is present
778    pub const fn is_repr_transparent(&self) -> bool {
779        matches!(self.repr, PRepr::Transparent)
780    }
781
782    /// Get the args of a builtin attribute with the given key (if present)
783    pub fn get_builtin_args(&self, key: &str) -> Option<String> {
784        self.facet
785            .iter()
786            .find(|a| a.is_builtin() && a.key_str() == key)
787            .map(|a| a.args.to_string().trim().trim_matches('"').to_string())
788    }
789
790    /// Get the facet crate path, defaulting to `::facet` if not specified
791    pub fn facet_crate(&self) -> TokenStream {
792        self.crate_path
793            .clone()
794            .unwrap_or_else(|| quote! { ::facet })
795    }
796
797    /// Check if any namespaced attribute exists (e.g., `xml::element`, `args::short`)
798    ///
799    /// When a namespaced attribute is present, `rename` on a container may be valid
800    /// because it controls how the type appears in that specific context.
801    pub fn has_any_namespaced(&self) -> bool {
802        self.facet.iter().any(|a| a.ns.is_some())
803    }
804
805    /// Get the span of a builtin attribute with the given key (if present)
806    pub fn get_builtin_span(&self, key: &str) -> Option<Span> {
807        self.facet
808            .iter()
809            .find(|a| a.is_builtin() && a.key_str() == key)
810            .map(|a| a.key.span())
811    }
812}
813
814/// Parsed container
815pub struct PContainer {
816    /// Original name of the container (could be a struct, an enum variant, etc.)
817    pub name: Ident,
818
819    /// If true, a `rename` attribute was applied and this is the result.
820    pub rename: Option<String>,
821
822    /// Attributes of the container
823    pub attrs: PAttrs,
824
825    /// Generic parameters of the container
826    pub bgp: BoundedGenericParams,
827}
828
829/// Parse struct
830pub struct PStruct {
831    /// Container information
832    pub container: PContainer,
833
834    /// Kind of struct
835    pub kind: PStructKind,
836}
837
838/// Parsed enum (given attributes etc.)
839pub struct PEnum {
840    /// Container information
841    pub container: PContainer,
842    /// The variants of the enum, in parsed form
843    pub variants: Vec<PVariant>,
844    /// The representation (repr) for the enum (e.g., C, u8, etc.)
845    pub repr: PRepr,
846}
847
848impl PEnum {
849    /// Parse a `crate::Enum` into a `PEnum`.
850    pub fn parse(e: &crate::Enum) -> Self {
851        // Parse container-level attributes (including repr and any errors)
852        let attrs = PAttrs::parse(&e.attributes);
853
854        // Get the container-level rename_all rule
855        let container_rename_all_rule = attrs.rename_all;
856
857        // Get repr from already-parsed attrs
858        let repr = attrs.repr;
859
860        // Build PContainer
861        let container = PContainer {
862            name: e.name.clone(),
863            rename: attrs.rename.clone(),
864            attrs,
865            bgp: BoundedGenericParams::parse(e.generics.as_ref()),
866        };
867
868        // Parse variants, passing the container's rename_all rule
869        let variants = e
870            .body
871            .content
872            .iter()
873            .map(|delim| PVariant::parse(&delim.value, container_rename_all_rule))
874            .collect();
875
876        PEnum {
877            container,
878            variants,
879            repr,
880        }
881    }
882}
883
884/// Parsed field
885#[derive(Clone)]
886pub struct PStructField {
887    /// The field's name (with rename rules applied)
888    pub name: PName,
889
890    /// The field's type
891    pub ty: TokenStream,
892
893    /// The field's offset (can be an expression, like `offset_of!(self, field)`)
894    pub offset: TokenStream,
895
896    /// The field's attributes
897    pub attrs: PAttrs,
898}
899
900impl PStructField {
901    /// Parse a named struct field (usual struct).
902    pub fn from_struct_field(f: &crate::StructField, rename_all_rule: Option<RenameRule>) -> Self {
903        use crate::ToTokens;
904        Self::parse_field(
905            &f.attributes,
906            IdentOrLiteral::Ident(f.name.clone()),
907            f.typ.to_token_stream(),
908            rename_all_rule,
909        )
910    }
911
912    /// Parse a tuple (unnamed) field for tuple structs or enum tuple variants.
913    /// The index is converted to an identifier like `_0`, `_1`, etc.
914    pub fn from_enum_field(
915        attrs: &[crate::Attribute],
916        idx: usize,
917        typ: &crate::VerbatimUntil<crate::Comma>,
918        rename_all_rule: Option<RenameRule>,
919    ) -> Self {
920        use crate::ToTokens;
921        // Create an Ident from the index, using `_` prefix convention for tuple fields
922        let ty = typ.to_token_stream(); // Convert to TokenStream
923        Self::parse_field(attrs, IdentOrLiteral::Literal(idx), ty, rename_all_rule)
924    }
925
926    /// Central parse function used by both `from_struct_field` and `from_enum_field`.
927    fn parse_field(
928        attrs: &[crate::Attribute],
929        name: IdentOrLiteral,
930        ty: TokenStream,
931        rename_all_rule: Option<RenameRule>,
932    ) -> Self {
933        // Parse attributes for the field
934        let attrs = PAttrs::parse(attrs);
935
936        let p_name = PName::new(name, rename_all_rule, attrs.rename.clone());
937
938        // Field type as TokenStream (already provided as argument)
939        let ty = ty.clone();
940
941        // Offset string -- we don't know the offset here in generic parsing, so just default to empty
942        let offset = quote! {};
943
944        PStructField {
945            name: p_name,
946            ty,
947            offset,
948            attrs,
949        }
950    }
951}
952/// Parsed struct kind, modeled after `StructKind`.
953pub enum PStructKind {
954    /// A regular struct with named fields.
955    Struct {
956        /// The struct fields
957        fields: Vec<PStructField>,
958    },
959    /// A tuple struct.
960    TupleStruct {
961        /// The tuple fields
962        fields: Vec<PStructField>,
963    },
964    /// A unit struct.
965    UnitStruct,
966}
967
968impl PStructKind {
969    /// Parse a `crate::StructKind` into a `PStructKind`.
970    /// Passes rename_all_rule through to all PStructField parsing.
971    pub fn parse(kind: &crate::StructKind, rename_all_rule: Option<RenameRule>) -> Self {
972        match kind {
973            crate::StructKind::Struct { clauses: _, fields } => {
974                let parsed_fields = fields
975                    .content
976                    .iter()
977                    .map(|delim| PStructField::from_struct_field(&delim.value, rename_all_rule))
978                    .collect();
979                PStructKind::Struct {
980                    fields: parsed_fields,
981                }
982            }
983            crate::StructKind::TupleStruct {
984                fields,
985                clauses: _,
986                semi: _,
987            } => {
988                let parsed_fields = fields
989                    .content
990                    .iter()
991                    .enumerate()
992                    .map(|(idx, delim)| {
993                        PStructField::from_enum_field(
994                            &delim.value.attributes,
995                            idx,
996                            &delim.value.typ,
997                            rename_all_rule,
998                        )
999                    })
1000                    .collect();
1001                PStructKind::TupleStruct {
1002                    fields: parsed_fields,
1003                }
1004            }
1005            crate::StructKind::UnitStruct {
1006                clauses: _,
1007                semi: _,
1008            } => PStructKind::UnitStruct,
1009        }
1010    }
1011}
1012
1013impl PStruct {
1014    /// Parse a struct into its parsed representation
1015    pub fn parse(s: &crate::Struct) -> Self {
1016        // Parse top-level (container) attributes for the struct.
1017        let attrs = PAttrs::parse(&s.attributes);
1018
1019        // Note: #[facet(rename = "...")] on structs is allowed. While for formats like JSON
1020        // the container name is determined by the parent field, formats like XML
1021        // use the container's rename as the element name (especially for root elements).
1022        // See: https://github.com/facet-rs/facet/issues/1018
1023
1024        // Extract the rename_all rule *after* parsing all attributes.
1025        let rename_all_rule = attrs.rename_all;
1026
1027        // Build PContainer from struct's name and attributes.
1028        let container = PContainer {
1029            name: s.name.clone(),
1030            rename: attrs.rename.clone(),
1031            attrs,
1032            bgp: BoundedGenericParams::parse(s.generics.as_ref()),
1033        };
1034
1035        // Pass the container's rename_all rule (extracted above) as argument to PStructKind::parse
1036        let kind = PStructKind::parse(&s.kind, rename_all_rule);
1037
1038        PStruct { container, kind }
1039    }
1040}
1041
1042/// Parsed enum variant kind
1043pub enum PVariantKind {
1044    /// Unit variant, e.g., `Variant`.
1045    Unit,
1046    /// Tuple variant, e.g., `Variant(u32, String)`.
1047    Tuple {
1048        /// The tuple variant fields
1049        fields: Vec<PStructField>,
1050    },
1051    /// Struct variant, e.g., `Variant { field1: u32, field2: String }`.
1052    Struct {
1053        /// The struct variant fields
1054        fields: Vec<PStructField>,
1055    },
1056}
1057
1058/// Parsed enum variant
1059pub struct PVariant {
1060    /// Name of the variant (with rename rules applied)
1061    pub name: PName,
1062    /// Attributes of the variant
1063    pub attrs: PAttrs,
1064    /// Kind of the variant (unit, tuple, or struct)
1065    pub kind: PVariantKind,
1066    /// Optional explicit discriminant (`= literal`)
1067    pub discriminant: Option<TokenStream>,
1068}
1069
1070impl PVariant {
1071    /// Parses an `EnumVariantLike` from `facet_macros_parse` into a `PVariant`.
1072    ///
1073    /// Requires the container-level `rename_all` rule to correctly determine the
1074    /// effective name of the variant itself. The variant's own `rename_all` rule
1075    /// (if present) will be stored in `attrs.rename_all` and used for its fields.
1076    fn parse(
1077        var_like: &crate::EnumVariantLike,
1078        container_rename_all_rule: Option<RenameRule>,
1079    ) -> Self {
1080        use crate::{EnumVariantData, StructEnumVariant, TupleVariant, UnitVariant};
1081
1082        let (raw_name_ident, attributes) = match &var_like.variant {
1083            // Fix: Changed var_like.value.variant to var_like.variant
1084            EnumVariantData::Unit(UnitVariant { name, attributes })
1085            | EnumVariantData::Tuple(TupleVariant {
1086                name, attributes, ..
1087            })
1088            | EnumVariantData::Struct(StructEnumVariant {
1089                name, attributes, ..
1090            }) => (name, attributes),
1091        };
1092
1093        // Parse variant attributes
1094        let attrs = PAttrs::parse(attributes.as_slice());
1095
1096        let name = PName::new(
1097            IdentOrLiteral::Ident(raw_name_ident.clone()),
1098            container_rename_all_rule,
1099            attrs.rename.clone(),
1100        );
1101
1102        // Extract the variant's own rename_all rule to apply to its fields
1103        let variant_field_rename_rule = attrs.rename_all;
1104
1105        // Parse the variant kind and its fields
1106        let kind = match &var_like.variant {
1107            // Fix: Changed var_like.value.variant to var_like.variant
1108            EnumVariantData::Unit(_) => PVariantKind::Unit,
1109            EnumVariantData::Tuple(TupleVariant { fields, .. }) => {
1110                let parsed_fields = fields
1111                    .content
1112                    .iter()
1113                    .enumerate()
1114                    .map(|(idx, delim)| {
1115                        PStructField::from_enum_field(
1116                            &delim.value.attributes,
1117                            idx,
1118                            &delim.value.typ,
1119                            variant_field_rename_rule, // Use variant's rule for its fields
1120                        )
1121                    })
1122                    .collect();
1123                PVariantKind::Tuple {
1124                    fields: parsed_fields,
1125                }
1126            }
1127            EnumVariantData::Struct(StructEnumVariant { fields, .. }) => {
1128                let parsed_fields = fields
1129                    .content
1130                    .iter()
1131                    .map(|delim| {
1132                        PStructField::from_struct_field(
1133                            &delim.value,
1134                            variant_field_rename_rule, // Use variant's rule for its fields
1135                        )
1136                    })
1137                    .collect();
1138                PVariantKind::Struct {
1139                    fields: parsed_fields,
1140                }
1141            }
1142        };
1143
1144        // Extract the discriminant literal if present
1145        let discriminant = var_like
1146            .discriminant
1147            .as_ref()
1148            .map(|d| d.second.to_token_stream());
1149
1150        PVariant {
1151            name,
1152            attrs,
1153            kind,
1154            discriminant,
1155        }
1156    }
1157}