facet_derive/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4mod process_enum;
5mod process_struct;
6
7use unsynn::*;
8
9keyword! {
10    KPub = "pub";
11    KStruct = "struct";
12    KEnum = "enum";
13    KDoc = "doc";
14    KRepr = "repr";
15    KCrate = "crate";
16    KConst = "const";
17    KMut = "mut";
18    KFacet = "facet";
19    KSensitive = "sensitive";
20}
21
22operator! {
23    Eq = "=";
24    Semi = ";";
25    Apostrophe = "'";
26    DoubleSemicolon = "::";
27}
28
29unsynn! {
30    enum TypeDecl {
31        Struct(Struct),
32        Enum(Enum),
33    }
34
35    enum Vis {
36        Pub(KPub),
37        PubCrate(Cons<KPub, ParenthesisGroupContaining<KCrate>>),
38    }
39
40    struct Attribute {
41        _pound: Pound,
42        body: BracketGroupContaining<AttributeInner>,
43    }
44
45    enum AttributeInner {
46        Facet(FacetAttr),
47        Doc(DocInner),
48        Repr(ReprInner),
49        Any(Vec<TokenTree>)
50    }
51
52    struct FacetAttr {
53        _facet: KFacet,
54        inner: ParenthesisGroupContaining<FacetInner>,
55    }
56
57    enum FacetInner {
58        Sensitive(KSensitive),
59        Other(Vec<TokenTree>)
60    }
61
62    struct DocInner {
63        _kw_doc: KDoc,
64        _eq: Eq,
65        value: LiteralString,
66    }
67
68    struct ReprInner {
69        _kw_repr: KRepr,
70        attr: ParenthesisGroupContaining<Ident>,
71    }
72
73    struct Struct {
74        attributes: Vec<Attribute>,
75        _vis: Option<Vis>,
76        _kw_struct: KStruct,
77        name: Ident,
78        // if None, Unit struct
79        body: Option<StructBody>,
80    }
81
82    enum StructBody {
83        Struct(BraceGroupContaining<CommaDelimitedVec<StructField>>),
84        TupleStruct(ParenthesisGroupContaining<CommaDelimitedVec<TupleField>>),
85    }
86
87    struct Lifetime {
88        _apostrophe: Apostrophe,
89        name: Ident,
90    }
91
92    enum Expr {
93        Integer(LiteralInteger),
94    }
95
96    enum Type {
97        Reference(ReferenceType),
98        Path(PathType),
99        Tuple(ParenthesisGroupContaining<CommaDelimitedVec<Box<Type>>>),
100        Slice(BracketGroupContaining<Box<Type>>),
101        Bare(BareType),
102    }
103
104    struct ReferenceType {
105        _and: And,
106        lifetime: Lifetime,
107        rest: Box<Type>,
108    }
109
110    struct PathType {
111        prefix: Ident,
112        _doublesemi: DoubleSemicolon,
113        rest: Box<Type>,
114    }
115
116    struct BareType {
117        name: Ident,
118        generic_params: Option<GenericParams>,
119    }
120
121    struct GenericParams {
122        _lt: Lt,
123        lifetimes: CommaDelimitedVec<Lifetime>,
124        params: CommaDelimitedVec<Type>,
125        _gt: Gt,
126    }
127
128    enum ConstOrMut {
129        Const(KConst),
130        Mut(KMut),
131    }
132
133    struct StructField {
134        attributes: Vec<Attribute>,
135        _vis: Option<Vis>,
136        name: Ident,
137        _colon: Colon,
138        typ: Type,
139    }
140
141    struct TupleField {
142        attributes: Vec<Attribute>,
143        vis: Option<Vis>,
144        typ: Type,
145    }
146
147    struct Enum {
148        attributes: Vec<Attribute>,
149        _pub: Option<KPub>,
150        _kw_enum: KEnum,
151        name: Ident,
152        body: BraceGroupContaining<CommaDelimitedVec<EnumVariantLike>>,
153    }
154
155    enum EnumVariantLike {
156        Tuple(TupleVariant),
157        Struct(StructVariant),
158        Unit(UnitVariant),
159    }
160
161    struct UnitVariant {
162        attributes: Vec<Attribute>,
163        name: Ident,
164    }
165
166    struct TupleVariant {
167        attributes: Vec<Attribute>,
168        name: Ident,
169        fields: ParenthesisGroupContaining<CommaDelimitedVec<TupleField>>,
170    }
171
172    struct StructVariant {
173        attributes: Vec<Attribute>,
174        name: Ident,
175        fields: BraceGroupContaining<CommaDelimitedVec<StructField>>,
176    }
177}
178
179/// Derive the Facet trait for structs, tuple structs, and enums.
180///
181/// This uses unsynn, so it's light, but it _will_ choke on some Rust syntax because...
182/// there's a lot of Rust syntax.
183#[proc_macro_derive(Facet, attributes(facet))]
184pub fn facet_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
185    let input = TokenStream::from(input);
186    let mut i = input.to_token_iter();
187
188    // Parse as TypeDecl
189    match i.parse::<TypeDecl>() {
190        Ok(TypeDecl::Struct(parsed)) => process_struct::process_struct(parsed),
191        Ok(TypeDecl::Enum(parsed)) => process_enum::process_enum(parsed),
192        Err(err) => {
193            panic!(
194                "Could not parse type declaration: {}\nError: {:?}",
195                input, err
196            );
197        }
198    }
199}
200
201impl core::fmt::Display for Type {
202    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
203        match self {
204            Type::Reference(reference) => {
205                write!(f, "&{} {}", reference.lifetime, reference.rest)
206            }
207            Type::Path(path) => {
208                write!(f, "{}::{}", path.prefix, path.rest)
209            }
210            Type::Tuple(tuple) => {
211                write!(f, "(")?;
212                for (i, typ) in tuple.content.0.iter().enumerate() {
213                    if i > 0 {
214                        write!(f, ", ")?;
215                    }
216                    write!(f, "{}", typ.value)?;
217                }
218                write!(f, ")")
219            }
220            Type::Slice(slice) => {
221                write!(f, "[{}]", slice.content)
222            }
223            Type::Bare(ident) => {
224                write!(f, "{}", ident.name)?;
225                if let Some(generic_params) = &ident.generic_params {
226                    write!(f, "<")?;
227                    for (i, param) in generic_params.params.0.iter().enumerate() {
228                        if i > 0 {
229                            write!(f, ", ")?;
230                        }
231                        write!(f, "{}", param.value)?;
232                    }
233                    write!(f, ">")?;
234                }
235                Ok(())
236            }
237        }
238    }
239}
240
241impl core::fmt::Display for ConstOrMut {
242    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243        match self {
244            ConstOrMut::Const(_) => write!(f, "const"),
245            ConstOrMut::Mut(_) => write!(f, "mut"),
246        }
247    }
248}
249
250impl core::fmt::Display for Lifetime {
251    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
252        write!(f, "'{}", self.name)
253    }
254}
255
256impl core::fmt::Display for Expr {
257    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
258        match self {
259            Expr::Integer(int) => write!(f, "{}", int.value()),
260        }
261    }
262}
263
264/// Converts PascalCase to UPPER_SNAKE_CASE
265pub(crate) fn to_upper_snake_case(input: &str) -> String {
266    input
267        .chars()
268        .enumerate()
269        .fold(String::new(), |mut acc, (i, c)| {
270            if c.is_uppercase() {
271                if i > 0 {
272                    acc.push('_');
273                }
274                acc.push(c.to_ascii_uppercase());
275            } else {
276                acc.push(c.to_ascii_uppercase());
277            }
278            acc
279        })
280}
281
282/// Generate a static declaration that exports the crate
283pub(crate) fn generate_static_decl(type_name: &str) -> String {
284    format!(
285        "#[used]\nstatic {}_SHAPE: &'static facet::Shape = <{} as facet::Facet>::SHAPE;",
286        to_upper_snake_case(type_name),
287        type_name
288    )
289}
290
291pub(crate) fn build_maybe_doc(attrs: &[Attribute]) -> String {
292    let doc_lines: Vec<_> = attrs
293        .iter()
294        .filter_map(|attr| match &attr.body.content {
295            AttributeInner::Doc(doc_inner) => Some(doc_inner.value.value()),
296            _ => None,
297        })
298        .collect();
299
300    if doc_lines.is_empty() {
301        String::new()
302    } else {
303        format!(r#".doc(&[{}])"#, doc_lines.join(","))
304    }
305}
306
307pub(crate) fn gen_struct_field(field_name: &str, struct_name: &str, attrs: &[Attribute]) -> String {
308    // Determine field flags
309    let mut flags = "facet::FieldFlags::EMPTY";
310    let mut attribute_list: Vec<String> = vec![];
311    let mut doc_lines: Vec<&str> = vec![];
312    for attr in attrs {
313        match &attr.body.content {
314            AttributeInner::Facet(facet_attr) => match &facet_attr.inner.content {
315                FacetInner::Sensitive(_ksensitive) => {
316                    flags = "facet::FieldFlags::SENSITIVE";
317                    attribute_list.push("facet::FieldAttribute::Sensitive".to_string());
318                }
319                FacetInner::Other(tt) => {
320                    attribute_list.push(format!(
321                        r#"facet::FieldAttribute::Arbitrary({:?})"#,
322                        tt.tokens_to_string()
323                    ));
324                }
325            },
326            AttributeInner::Doc(doc_inner) => doc_lines.push(doc_inner.value.value()),
327            AttributeInner::Repr(_) => {
328                // muffin
329            }
330            AttributeInner::Any(_) => {
331                // muffin two
332            }
333        }
334    }
335    let attributes = attribute_list.join(",");
336
337    let maybe_field_doc = if doc_lines.is_empty() {
338        String::new()
339    } else {
340        format!(r#".doc(&[{}])"#, doc_lines.join(","))
341    };
342
343    // Generate each field definition
344    format!(
345        "facet::Field::builder()
346    .name(\"{field_name}\")
347    .shape(facet::shape_of(&|s: {struct_name}| s.{field_name}))
348    .offset(::core::mem::offset_of!({struct_name}, {field_name}))
349    .flags({flags})
350    .attributes(&[{attributes}])
351    {maybe_field_doc}
352    .build()"
353    )
354}