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(tokens) => {
584                    // WORKAROUND: Doc comments with raw string literals (r"...") are not
585                    // recognized by the DocInner parser, so they end up as Any attributes.
586                    // Parse them manually here: doc = <string literal>
587                    if tokens.len() == 3
588                        && let Some(proc_macro2::TokenTree::Ident(id)) = tokens.first()
589                        && id == "doc"
590                        && let Some(proc_macro2::TokenTree::Literal(lit)) = tokens.get(2)
591                    {
592                        // Extract the string value from the literal
593                        let lit_str = lit.to_string();
594                        // Handle both regular strings "..." and raw strings r"..."
595                        let content = if lit_str.starts_with("r#") {
596                            // Raw string with hashes: r#"..."#, r##"..."##, etc.
597                            let hash_count =
598                                lit_str.chars().skip(1).take_while(|&c| c == '#').count();
599                            let start = 2 + hash_count + 1; // r + hashes + "
600                            let end = lit_str.len() - 1 - hash_count; // " + hashes
601                            lit_str[start..end].to_string()
602                        } else if lit_str.starts_with('r') {
603                            // Simple raw string: r"..."
604                            let content = &lit_str[2..lit_str.len() - 1];
605                            content.to_string()
606                        } else {
607                            // Regular string: "..." - needs unescaping
608                            let trimmed = lit_str.trim_matches('"');
609                            match crate::unescape_inner(trimmed) {
610                                Ok(s) => s,
611                                Err(_) => continue, // Skip malformed strings
612                            }
613                        };
614                        doc_lines.push(content);
615                    }
616                }
617            }
618        }
619
620        // Extract rename, rename_all, crate, traits, and auto_traits from parsed attrs
621        let mut declared_traits: Option<DeclaredTraits> = None;
622        let mut auto_traits = false;
623
624        for attr in &facet_attrs {
625            if attr.is_builtin() {
626                match attr.key_str().as_str() {
627                    "rename" => {
628                        let s = attr.args.to_string();
629                        let trimmed = s.trim().trim_matches('"');
630                        *display_name = trimmed.to_string();
631                    }
632                    "rename_all" => {
633                        let s = attr.args.to_string();
634                        let rule_str = s.trim().trim_matches('"');
635                        if let Some(rule) = RenameRule::parse(rule_str) {
636                            rename_all = Some(rule);
637                        } else {
638                            errors.push(CompileError {
639                                message: format!(
640                                    "unknown #[facet(rename_all = \"...\")] rule: `{rule_str}`. \
641                                     Valid options: camelCase, snake_case, kebab-case, \
642                                     PascalCase, SCREAMING_SNAKE_CASE, SCREAMING-KEBAB-CASE, \
643                                     lowercase, UPPERCASE"
644                                ),
645                                span: attr.key.span(),
646                            });
647                        }
648                    }
649                    "crate" => {
650                        // Store the crate path tokens directly
651                        crate_path = Some(attr.args.clone());
652                    }
653                    "traits" => {
654                        // Parse #[facet(traits(Debug, PartialEq, Clone, ...))]
655                        declared_traits =
656                            Some(DeclaredTraits::parse_from_tokens(&attr.args, &mut errors));
657                    }
658                    "auto_traits" => {
659                        // #[facet(auto_traits)] enables specialization-based detection
660                        auto_traits = true;
661                    }
662                    _ => {}
663                }
664            }
665        }
666
667        // Validate: traits(...) and auto_traits are mutually exclusive
668        if declared_traits.is_some()
669            && auto_traits
670            && let Some(span) = facet_attrs
671                .iter()
672                .find(|a| a.is_builtin() && a.key_str() == "auto_traits")
673                .map(|a| a.key.span())
674        {
675            errors.push(CompileError {
676                message: "cannot use both #[facet(traits(...))] and #[facet(auto_traits)] \
677                              on the same type"
678                    .to_string(),
679                span,
680            });
681        }
682
683        Self {
684            doc: doc_lines,
685            facet: facet_attrs,
686            repr: repr.unwrap_or(PRepr::Rust(None)),
687            rename_all,
688            crate_path,
689            errors,
690            declared_traits,
691            auto_traits,
692        }
693    }
694
695    /// Check if a builtin attribute with the given key exists
696    pub fn has_builtin(&self, key: &str) -> bool {
697        self.facet
698            .iter()
699            .any(|a| a.is_builtin() && a.key_str() == key)
700    }
701
702    /// Check if `#[repr(transparent)]` is present
703    pub fn is_repr_transparent(&self) -> bool {
704        matches!(self.repr, PRepr::Transparent)
705    }
706
707    /// Get the args of a builtin attribute with the given key (if present)
708    pub fn get_builtin_args(&self, key: &str) -> Option<String> {
709        self.facet
710            .iter()
711            .find(|a| a.is_builtin() && a.key_str() == key)
712            .map(|a| a.args.to_string().trim().trim_matches('"').to_string())
713    }
714
715    /// Get the facet crate path, defaulting to `::facet` if not specified
716    pub fn facet_crate(&self) -> TokenStream {
717        self.crate_path
718            .clone()
719            .unwrap_or_else(|| quote! { ::facet })
720    }
721
722    /// Check if any namespaced attribute exists (e.g., `kdl::child`, `args::short`)
723    ///
724    /// When a namespaced attribute is present, `rename` on a container may be valid
725    /// because it controls how the type appears in that specific context.
726    pub fn has_any_namespaced(&self) -> bool {
727        self.facet.iter().any(|a| a.ns.is_some())
728    }
729
730    /// Get the span of a builtin attribute with the given key (if present)
731    pub fn get_builtin_span(&self, key: &str) -> Option<Span> {
732        self.facet
733            .iter()
734            .find(|a| a.is_builtin() && a.key_str() == key)
735            .map(|a| a.key.span())
736    }
737}
738
739/// Parsed container
740pub struct PContainer {
741    /// Name of the container (could be a struct, an enum variant, etc.)
742    pub name: Ident,
743
744    /// Attributes of the container
745    pub attrs: PAttrs,
746
747    /// Generic parameters of the container
748    pub bgp: BoundedGenericParams,
749}
750
751/// Parse struct
752pub struct PStruct {
753    /// Container information
754    pub container: PContainer,
755
756    /// Kind of struct
757    pub kind: PStructKind,
758}
759
760/// Parsed enum (given attributes etc.)
761pub struct PEnum {
762    /// Container information
763    pub container: PContainer,
764    /// The variants of the enum, in parsed form
765    pub variants: Vec<PVariant>,
766    /// The representation (repr) for the enum (e.g., C, u8, etc.)
767    pub repr: PRepr,
768}
769
770impl PEnum {
771    /// Parse a `crate::Enum` into a `PEnum`.
772    pub fn parse(e: &crate::Enum) -> Self {
773        let mut container_display_name = e.name.to_string();
774
775        // Parse container-level attributes (including repr and any errors)
776        let attrs = PAttrs::parse(&e.attributes, &mut container_display_name);
777
778        // Get the container-level rename_all rule
779        let container_rename_all_rule = attrs.rename_all;
780
781        // Get repr from already-parsed attrs
782        let repr = attrs.repr;
783
784        // Build PContainer
785        let container = PContainer {
786            name: e.name.clone(),
787            attrs,
788            bgp: BoundedGenericParams::parse(e.generics.as_ref()),
789        };
790
791        // Parse variants, passing the container's rename_all rule
792        let variants = e
793            .body
794            .content
795            .iter()
796            .map(|delim| PVariant::parse(&delim.value, container_rename_all_rule))
797            .collect();
798
799        PEnum {
800            container,
801            variants,
802            repr,
803        }
804    }
805}
806
807/// Parsed field
808#[derive(Clone)]
809pub struct PStructField {
810    /// The field's name (with rename rules applied)
811    pub name: PName,
812
813    /// The field's type
814    pub ty: TokenStream,
815
816    /// The field's offset (can be an expression, like `offset_of!(self, field)`)
817    pub offset: TokenStream,
818
819    /// The field's attributes
820    pub attrs: PAttrs,
821}
822
823impl PStructField {
824    /// Parse a named struct field (usual struct).
825    pub fn from_struct_field(f: &crate::StructField, rename_all_rule: Option<RenameRule>) -> Self {
826        use crate::ToTokens;
827        Self::parse_field(
828            &f.attributes,
829            IdentOrLiteral::Ident(f.name.clone()),
830            f.typ.to_token_stream(),
831            rename_all_rule,
832        )
833    }
834
835    /// Parse a tuple (unnamed) field for tuple structs or enum tuple variants.
836    /// The index is converted to an identifier like `_0`, `_1`, etc.
837    pub fn from_enum_field(
838        attrs: &[crate::Attribute],
839        idx: usize,
840        typ: &crate::VerbatimUntil<crate::Comma>,
841        rename_all_rule: Option<RenameRule>,
842    ) -> Self {
843        use crate::ToTokens;
844        // Create an Ident from the index, using `_` prefix convention for tuple fields
845        let ty = typ.to_token_stream(); // Convert to TokenStream
846        Self::parse_field(attrs, IdentOrLiteral::Literal(idx), ty, rename_all_rule)
847    }
848
849    /// Central parse function used by both `from_struct_field` and `from_enum_field`.
850    fn parse_field(
851        attrs: &[crate::Attribute],
852        name: IdentOrLiteral,
853        ty: TokenStream,
854        rename_all_rule: Option<RenameRule>,
855    ) -> Self {
856        let initial_display_name = quote::ToTokens::to_token_stream(&name).tokens_to_string();
857        let mut display_name = initial_display_name.clone();
858
859        // Parse attributes for the field
860        let attrs = PAttrs::parse(attrs, &mut display_name);
861
862        // Name resolution:
863        // Precedence:
864        //   1. Field-level #[facet(rename = "...")]
865        //   2. rename_all_rule argument (container-level rename_all, passed in)
866        //   3. Raw field name (after stripping "r#")
867        let raw = name.clone();
868
869        let p_name = if display_name != initial_display_name {
870            // If #[facet(rename = "...")] is present, use it directly as the effective name.
871            // Preserve the span of the original identifier.
872            PName {
873                raw: raw.clone(),
874                effective: display_name,
875            }
876        } else {
877            // Use PName::new logic with container_rename_rule as the rename_all_rule argument.
878            // PName::new handles the case where rename_all_rule is None.
879            PName::new(rename_all_rule, raw)
880        };
881
882        // Field type as TokenStream (already provided as argument)
883        let ty = ty.clone();
884
885        // Offset string -- we don't know the offset here in generic parsing, so just default to empty
886        let offset = quote! {};
887
888        PStructField {
889            name: p_name,
890            ty,
891            offset,
892            attrs,
893        }
894    }
895}
896/// Parsed struct kind, modeled after `StructKind`.
897pub enum PStructKind {
898    /// A regular struct with named fields.
899    Struct {
900        /// The struct fields
901        fields: Vec<PStructField>,
902    },
903    /// A tuple struct.
904    TupleStruct {
905        /// The tuple fields
906        fields: Vec<PStructField>,
907    },
908    /// A unit struct.
909    UnitStruct,
910}
911
912impl PStructKind {
913    /// Parse a `crate::StructKind` into a `PStructKind`.
914    /// Passes rename_all_rule through to all PStructField parsing.
915    pub fn parse(kind: &crate::StructKind, rename_all_rule: Option<RenameRule>) -> Self {
916        match kind {
917            crate::StructKind::Struct { clauses: _, fields } => {
918                let parsed_fields = fields
919                    .content
920                    .iter()
921                    .map(|delim| PStructField::from_struct_field(&delim.value, rename_all_rule))
922                    .collect();
923                PStructKind::Struct {
924                    fields: parsed_fields,
925                }
926            }
927            crate::StructKind::TupleStruct {
928                fields,
929                clauses: _,
930                semi: _,
931            } => {
932                let parsed_fields = fields
933                    .content
934                    .iter()
935                    .enumerate()
936                    .map(|(idx, delim)| {
937                        PStructField::from_enum_field(
938                            &delim.value.attributes,
939                            idx,
940                            &delim.value.typ,
941                            rename_all_rule,
942                        )
943                    })
944                    .collect();
945                PStructKind::TupleStruct {
946                    fields: parsed_fields,
947                }
948            }
949            crate::StructKind::UnitStruct {
950                clauses: _,
951                semi: _,
952            } => PStructKind::UnitStruct,
953        }
954    }
955}
956
957impl PStruct {
958    /// Parse a struct into its parsed representation
959    pub fn parse(s: &crate::Struct) -> Self {
960        let original_name = s.name.to_string();
961        let mut container_display_name = original_name.clone();
962
963        // Parse top-level (container) attributes for the struct.
964        let attrs = PAttrs::parse(&s.attributes, &mut container_display_name);
965
966        // Note: #[facet(rename = "...")] on structs is allowed. While for formats like JSON
967        // the container name is determined by the parent field, formats like XML and KDL
968        // use the container's rename as the element/node name (especially for root elements).
969        // See: https://github.com/facet-rs/facet/issues/1018
970
971        // Extract the rename_all rule *after* parsing all attributes.
972        let rename_all_rule = attrs.rename_all;
973
974        // Build PContainer from struct's name and attributes.
975        let container = PContainer {
976            name: s.name.clone(),
977            attrs, // Use the parsed attributes (which includes rename_all implicitly)
978            bgp: BoundedGenericParams::parse(s.generics.as_ref()),
979        };
980
981        // Pass the container's rename_all rule (extracted above) as argument to PStructKind::parse
982        let kind = PStructKind::parse(&s.kind, rename_all_rule);
983
984        PStruct { container, kind }
985    }
986}
987
988/// Parsed enum variant kind
989pub enum PVariantKind {
990    /// Unit variant, e.g., `Variant`.
991    Unit,
992    /// Tuple variant, e.g., `Variant(u32, String)`.
993    Tuple {
994        /// The tuple variant fields
995        fields: Vec<PStructField>,
996    },
997    /// Struct variant, e.g., `Variant { field1: u32, field2: String }`.
998    Struct {
999        /// The struct variant fields
1000        fields: Vec<PStructField>,
1001    },
1002}
1003
1004/// Parsed enum variant
1005pub struct PVariant {
1006    /// Name of the variant (with rename rules applied)
1007    pub name: PName,
1008    /// Attributes of the variant
1009    pub attrs: PAttrs,
1010    /// Kind of the variant (unit, tuple, or struct)
1011    pub kind: PVariantKind,
1012    /// Optional explicit discriminant (`= literal`)
1013    pub discriminant: Option<TokenStream>,
1014}
1015
1016impl PVariant {
1017    /// Parses an `EnumVariantLike` from `facet_macros_parse` into a `PVariant`.
1018    ///
1019    /// Requires the container-level `rename_all` rule to correctly determine the
1020    /// effective name of the variant itself. The variant's own `rename_all` rule
1021    /// (if present) will be stored in `attrs.rename_all` and used for its fields.
1022    fn parse(
1023        var_like: &crate::EnumVariantLike,
1024        container_rename_all_rule: Option<RenameRule>,
1025    ) -> Self {
1026        use crate::{EnumVariantData, StructEnumVariant, TupleVariant, UnitVariant};
1027
1028        let (raw_name_ident, attributes) = match &var_like.variant {
1029            // Fix: Changed var_like.value.variant to var_like.variant
1030            EnumVariantData::Unit(UnitVariant { name, attributes })
1031            | EnumVariantData::Tuple(TupleVariant {
1032                name, attributes, ..
1033            })
1034            | EnumVariantData::Struct(StructEnumVariant {
1035                name, attributes, ..
1036            }) => (name, attributes),
1037        };
1038
1039        let initial_display_name = raw_name_ident.to_string();
1040        let mut display_name = initial_display_name.clone();
1041
1042        // Parse variant attributes, potentially modifying display_name if #[facet(rename=...)] is found
1043        let attrs = PAttrs::parse(attributes.as_slice(), &mut display_name); // Fix: Pass attributes as a slice
1044
1045        // Determine the variant's effective name
1046        let name = if display_name != initial_display_name {
1047            // #[facet(rename=...)] was present on the variant
1048            PName {
1049                raw: IdentOrLiteral::Ident(raw_name_ident.clone()),
1050                effective: display_name,
1051            }
1052        } else {
1053            // Use container's rename_all rule if no variant-specific rename found
1054            PName::new(
1055                container_rename_all_rule,
1056                IdentOrLiteral::Ident(raw_name_ident.clone()),
1057            )
1058        };
1059
1060        // Extract the variant's own rename_all rule to apply to its fields
1061        let variant_field_rename_rule = attrs.rename_all;
1062
1063        // Parse the variant kind and its fields
1064        let kind = match &var_like.variant {
1065            // Fix: Changed var_like.value.variant to var_like.variant
1066            EnumVariantData::Unit(_) => PVariantKind::Unit,
1067            EnumVariantData::Tuple(TupleVariant { fields, .. }) => {
1068                let parsed_fields = fields
1069                    .content
1070                    .iter()
1071                    .enumerate()
1072                    .map(|(idx, delim)| {
1073                        PStructField::from_enum_field(
1074                            &delim.value.attributes,
1075                            idx,
1076                            &delim.value.typ,
1077                            variant_field_rename_rule, // Use variant's rule for its fields
1078                        )
1079                    })
1080                    .collect();
1081                PVariantKind::Tuple {
1082                    fields: parsed_fields,
1083                }
1084            }
1085            EnumVariantData::Struct(StructEnumVariant { fields, .. }) => {
1086                let parsed_fields = fields
1087                    .content
1088                    .iter()
1089                    .map(|delim| {
1090                        PStructField::from_struct_field(
1091                            &delim.value,
1092                            variant_field_rename_rule, // Use variant's rule for its fields
1093                        )
1094                    })
1095                    .collect();
1096                PVariantKind::Struct {
1097                    fields: parsed_fields,
1098                }
1099            }
1100        };
1101
1102        // Extract the discriminant literal if present
1103        let discriminant = var_like
1104            .discriminant
1105            .as_ref()
1106            .map(|d| d.second.to_token_stream());
1107
1108        PVariant {
1109            name,
1110            attrs,
1111            kind,
1112            discriminant,
1113        }
1114    }
1115}