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