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