cuisiner_derive/
lib.rs

1mod analyse;
2mod codegen;
3mod lower;
4mod parse;
5
6use proc_macro2::TokenStream;
7use syn::{DeriveInput, Error, Ident, Meta, Token, Type, parenthesized, punctuated::Punctuated};
8
9use self::{analyse::*, codegen::*, lower::*, parse::*};
10
11#[proc_macro_derive(Cuisiner, attributes(cuisiner))]
12pub fn derive_cuisiner(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
13    // Parse the token stream.
14    let derive_input = syn::parse_macro_input!(ts as DeriveInput);
15
16    // Run the inner implementation, and handle `Error` cases.
17    match derive_cuisiner_inner(derive_input) {
18        Ok(ts) => ts,
19        Result::Err(e) => e.into_compile_error(),
20    }
21    .into()
22}
23
24/// Inner implementation of the derive, which simply glues together each of the different stages of
25/// the macro.
26fn derive_cuisiner_inner(derive_input: DeriveInput) -> Result<TokenStream, Error> {
27    let ast = parse(derive_input)?;
28    let model = analyse(ast)?;
29    let ir = lower(model)?;
30    codegen(ir)
31}
32
33/// All availble field representations. Similar to [`syn::Fields`].
34#[derive(Clone)]
35enum Fields {
36    /// Named fields ([`syn::FieldsNamed`]).
37    Named(Vec<(Ident, Type, Option<Vec<Meta>>)>),
38    /// Unnamed fields ([`syn::FieldsUnnamed`]).
39    Unnamed(Vec<(Type, Option<Vec<Meta>>)>),
40    /// No fields ([`syn::Fields::Unit`]).
41    Unit,
42}
43
44impl TryFrom<&syn::Fields> for Fields {
45    type Error = Error;
46
47    fn try_from(fields: &syn::Fields) -> Result<Self, Self::Error> {
48        Ok(match fields {
49            syn::Fields::Named(fields_named) => Fields::Named(
50                fields_named
51                    .named
52                    .iter()
53                    .map(|field| {
54                        let ident = field
55                            .ident
56                            .clone()
57                            .expect("named struct field must have ident");
58                        let ty = field.ty.clone();
59
60                        let mut assert_layout = None;
61                        for attr in &field.attrs {
62                            if !attr.path().is_ident("cuisiner") {
63                                continue;
64                            }
65
66                            attr.parse_nested_meta(|meta| {
67                                if meta.path.is_ident("assert") {
68                                    // Remove the parenthesis.
69                                    let args;
70                                    parenthesized!(args in meta.input);
71
72                                    // Fetch the meta items from the attributes.
73                                    assert_layout = Some(
74                                        Punctuated::<Meta, Token![,]>::parse_terminated(&args)?
75                                            .into_iter()
76                                            .collect(),
77                                    );
78
79                                    return Ok(());
80                                }
81
82                                Err(Error::new_spanned(&meta.path, "unknown attribute"))
83                            })?;
84                        }
85
86                        Ok((ident, ty, assert_layout))
87                    })
88                    .collect::<Result<_, Error>>()?,
89            ),
90            syn::Fields::Unnamed(fields_unnamed) => Fields::Unnamed(
91                fields_unnamed
92                    .unnamed
93                    .iter()
94                    .map(|field| {
95                        Ok((field.ty.clone(), {
96                            let mut assert_layout = None;
97                            for attr in &field.attrs {
98                                if !attr.path().is_ident("cuisiner") {
99                                    continue;
100                                }
101
102                                attr.parse_nested_meta(|meta| {
103                                    if meta.path.is_ident("assert") {
104                                        // Remove the parenthesis.
105                                        let args;
106                                        parenthesized!(args in meta.input);
107
108                                        // Fetch the meta items from the attributes.
109                                        assert_layout = Some(
110                                            Punctuated::<Meta, Token![,]>::parse_terminated(&args)?
111                                                .into_iter()
112                                                .collect(),
113                                        );
114
115                                        return Ok(());
116                                    }
117
118                                    Err(Error::new_spanned(&meta.path, "unknown attribute"))
119                                })?;
120                            }
121                            assert_layout
122                        }))
123                    })
124                    .collect::<Result<_, Error>>()?,
125            ),
126            syn::Fields::Unit => Fields::Unit,
127        })
128    }
129}