Skip to main content

servo_config_macro/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use proc_macro2::TokenStream;
6use quote::quote;
7use syn::{Data, Fields};
8use synstructure::decl_derive;
9
10decl_derive!([ServoPreferences] => servo_preferences_derive);
11
12/// A derive macro that adds string-based getter and setter for each field of this struct
13/// (enums and other types are not supported). Each field must be able to be convertable
14/// (with `into()`) into a `PrefValue`.
15fn servo_preferences_derive(input: synstructure::Structure) -> TokenStream {
16    let ast = input.ast();
17
18    let Data::Struct(ref data) = ast.data else {
19        unimplemented!();
20    };
21    let Fields::Named(ref named_fields) = data.fields else {
22        unimplemented!()
23    };
24
25    let mut exists_match_cases = quote!();
26    for field in named_fields.named.iter() {
27        let name = field.ident.as_ref().unwrap();
28        exists_match_cases.extend(quote!(stringify!(#name) => true,))
29    }
30
31    let mut get_match_cases = quote!();
32    for field in named_fields.named.iter() {
33        let name = field.ident.as_ref().unwrap();
34        get_match_cases.extend(quote!(stringify!(#name) => self.#name.clone().into(),))
35    }
36
37    let mut set_match_cases = quote!();
38    for field in named_fields.named.iter() {
39        let name = field.ident.as_ref().unwrap();
40        set_match_cases.extend(quote!(stringify!(#name) => self.#name = value.try_into().unwrap(),))
41    }
42
43    let mut type_of_match_cases = quote!();
44    for field in named_fields.named.iter() {
45        let name = field.ident.as_ref().unwrap();
46        let ty = &field.ty;
47        type_of_match_cases.extend(quote!(stringify!(#name) => std::any::type_name::<#ty>(),))
48    }
49
50    let mut comparisons = quote!();
51    for field in named_fields.named.iter() {
52        let name = field.ident.as_ref().unwrap();
53        comparisons.extend(quote!(if self.#name != other.#name { changes.push((stringify!(#name), self.#name.clone().into(),)) }))
54    }
55
56    let mut all_fields = quote!();
57    for field in named_fields.named.iter() {
58        let name = field.ident.as_ref().unwrap();
59        all_fields.extend(quote!(stringify!(#name),));
60    }
61
62    let structure_name = &ast.ident;
63    quote! {
64        impl #structure_name {
65            pub fn exists(name: &str) -> bool {
66                match name {
67                    #exists_match_cases
68                    _ => { false }
69                }
70            }
71
72            pub fn get_value(&self, name: &str) -> PrefValue {
73                match name {
74                    #get_match_cases
75                    _ => { panic!("Unknown preference: {:?}", name); }
76                }
77            }
78
79            pub fn set_value(&mut self, name: &str, value: PrefValue) {
80                match name {
81                    #set_match_cases
82                    _ => { panic!("Unknown preference: {:?}", name); }
83                }
84            }
85
86            pub fn type_of(name: &str) -> &'static str {
87                match name {
88                    #type_of_match_cases
89                    _ => { panic!("Unknown preference: {:?}", name); }
90                }
91            }
92
93            pub fn diff(&self, other: &Self) -> Vec<(&'static str, PrefValue)> {
94                let mut changes = vec![];
95                #comparisons
96                changes
97            }
98
99            pub fn all_fields() -> Vec<&'static str> {
100                vec![
101                    #all_fields
102                ]
103            }
104        }
105    }
106}