system_harness_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4    parse_macro_input, spanned::Spanned, AttrStyle, Attribute, Data, DataEnum, DataStruct,
5    DeriveInput, Field, Fields, FieldsNamed, Ident, LitStr, Variant,
6};
7
8type Result<T> = std::result::Result<T, syn::Error>;
9
10#[proc_macro_derive(PropertyList)]
11pub fn property_list(input: TokenStream) -> TokenStream {
12    let derive_input = parse_macro_input!(input as DeriveInput);
13    match impl_proplist(&derive_input) {
14        Ok(props) => props.into(),
15        Err(err) => err.into_compile_error().into(),
16    }
17}
18
19#[proc_macro_derive(Backend)]
20pub fn backend(input: TokenStream) -> TokenStream {
21    let derive_input = parse_macro_input!(input as DeriveInput);
22    match impl_backends(&derive_input) {
23        Ok(props) => props.into(),
24        Err(err) => err.into_compile_error().into(),
25    }
26}
27
28fn impl_proplist(input: &DeriveInput) -> Result<TokenStream> {
29    let ident = &input.ident;
30    match input.data {
31        Data::Struct(DataStruct {
32            struct_token: _,
33            ref fields,
34            semi_token: _,
35        }) => {
36            let insert_props = impl_insert_props(false, fields)?;
37            Ok(quote! {
38                impl cmdstruct::Arg for #ident {
39
40                    fn append_arg(&self, command: &mut std::process::Command) {
41                        #insert_props
42                        command.arg(&format!("{props}"));
43                    }
44
45                }
46            }
47            .into())
48        }
49        _ => Err(syn::Error::new(input.span(), "Only structs are supported.")),
50    }
51}
52
53fn field_identifiers(fields: &Fields) -> Result<Vec<Ident>> {
54    match &fields {
55        Fields::Named(FieldsNamed {
56            brace_token: _,
57            ref named,
58        }) => Ok(named
59            .clone()
60            .iter()
61            .filter_map(|field| field.ident.clone())
62            .collect()),
63        Fields::Unit => Ok(Vec::new()),
64        Fields::Unnamed(_) => Err(syn::Error::new(
65            fields.span(),
66            "Unnamed fields are not supported.",
67        )),
68    }
69}
70
71fn backend_name_matcher(tuple: (&Ident, &Variant)) -> Result<proc_macro2::TokenStream> {
72    let ident = &tuple.1.ident;
73    let enum_ident = &tuple.0;
74    let name = format!("{ident}").to_lowercase();
75    let fields = field_identifiers(&tuple.1.fields)?;
76    let enum_fields = if fields.is_empty() {
77        quote! {}
78    } else {
79        quote! { { #(#fields: _, )* } }
80    };
81    Ok(quote! {
82        #enum_ident::#ident #enum_fields => #name,
83    })
84}
85
86fn backend_properties_matcher(tuple: (&Ident, &Variant)) -> Result<proc_macro2::TokenStream> {
87    let ident = &tuple.1.ident;
88    let insert_props = impl_insert_props(true, &tuple.1.fields)?;
89    let enum_ident = &tuple.0;
90    let fields = field_identifiers(&tuple.1.fields)?;
91    let enum_fields = if fields.is_empty() {
92        quote! {}
93    } else {
94        quote! { { #(#fields, )* } }
95    };
96    let matcher = quote! {
97        #enum_ident::#ident #enum_fields => {
98            #insert_props
99            props
100        }
101    };
102    Ok(matcher)
103}
104
105fn impl_backends(input: &DeriveInput) -> Result<TokenStream> {
106    let ident = &input.ident;
107    match input.data {
108        Data::Enum(DataEnum {
109            enum_token: _,
110            brace_token: _,
111            ref variants,
112        }) => {
113            let name_matches: Vec<_> = variants
114                .iter()
115                .map(|variant| (ident, variant))
116                .map(backend_name_matcher)
117                .collect::<Result<Vec<_>>>()?;
118            let properties_matches: Vec<proc_macro2::TokenStream> = variants
119                .iter()
120                .map(|variant| (ident, variant))
121                .map(backend_properties_matcher)
122                .collect::<Result<Vec<_>>>()?;
123            Ok(quote! {
124                impl crate::qemu::args::Backend for #ident {
125
126                    fn name(&self) -> &str {
127                        match self {
128                            #(#name_matches)*
129                        }
130                    }
131
132                    fn properties<'backend>(&'backend self)
133                        -> crate::qemu::args::PropertyList<'backend> {
134                            match self {
135                                #(#properties_matches, )*
136                            }
137                    }
138
139                }
140            }
141            .into())
142        }
143        _ => Err(syn::Error::new(input.span(), "Only structs are supported.")),
144    }
145}
146
147enum SerdeAttribute {
148    Flatten,
149    Rename(LitStr),
150}
151
152fn insert_prop(local: bool, field: &Field) -> Option<proc_macro2::TokenStream> {
153    if let Some(ref ident) = field.ident {
154        let value = if local {
155            quote! { #ident }
156        } else {
157            quote! { &self.#ident }
158        };
159        let tokens = match parse_attributes(&field.attrs) {
160            Some(SerdeAttribute::Flatten) => quote! {
161                for (key, value) in #value {
162                    props.insert(key, value);
163                }
164            },
165            Some(SerdeAttribute::Rename(ref rename)) => {
166                let name_str = format!("{}", rename.value());
167                quote! {
168                    props.insert(#name_str, #value);
169                }
170            }
171            None => {
172                let name_str = format!("{ident}");
173                quote! {
174                    props.insert(#name_str, #value);
175                }
176            }
177        };
178        Some(tokens)
179    } else {
180        None
181    }
182}
183
184fn impl_insert_props(local: bool, fields: &Fields) -> Result<proc_macro2::TokenStream> {
185    match fields {
186        Fields::Named(FieldsNamed {
187            brace_token: _,
188            ref named,
189        }) => {
190            let insert_props: Vec<proc_macro2::TokenStream> = named
191                .iter()
192                .filter_map(|field| insert_prop(local, field))
193                .collect();
194            Ok(quote! {
195                let mut props = crate::qemu::args::PropertyList::default();
196                #(#insert_props)*
197            })
198        }
199        Fields::Unit => Ok(quote! {
200            let props = crate::qemu::args::PropertyList::default();
201        }),
202        _ => Err(syn::Error::new(
203            fields.span(),
204            "Unnamed fields are not supported",
205        )),
206    }
207}
208
209/// Check if a flattened serde attribute
210fn parse_attributes(attrs: &[Attribute]) -> Option<SerdeAttribute> {
211    let mut flatten = false;
212    let mut rename = None;
213    for attr in attrs {
214        match attr.style {
215            AttrStyle::Outer => match &attr.meta {
216                syn::Meta::List(list) if list.path.is_ident("serde") => {
217                    let _ = attr.parse_nested_meta(|meta| {
218                        if meta.path.is_ident("flatten") {
219                            flatten = true;
220                        } else if meta.path.is_ident("rename") {
221                            let value = meta.value()?;
222                            let s: LitStr = value.parse()?;
223                            rename = Some(LitStr::new(&s.value(), attr.span()));
224                        }
225                        Ok(())
226                    });
227                }
228                _ => {}
229            },
230            _ => {}
231        };
232    }
233    if flatten {
234        Some(SerdeAttribute::Flatten)
235    } else if let Some(rename) = rename {
236        Some(SerdeAttribute::Rename(rename))
237    } else {
238        None
239    }
240}