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