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}