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}