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