facet_macro_parse/
parsed.rs

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