glib_macros/
enum_derive.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase};
4use proc_macro2::TokenStream;
5use quote::{format_ident, quote, quote_spanned, ToTokens};
6
7use crate::utils::{
8    crate_ident_new, gen_enum_from_glib, parse_nested_meta_items, parse_optional_nested_meta_items,
9    NestedMetaItem,
10};
11
12// generates glib::gobject_ffi::GEnumValue structs mapping the enum such as:
13//     glib::gobject_ffi::GEnumValue {
14//         value: Animal::Goat as i32,
15//         value_name: "Goat\0" as *const _ as *const _,
16//         value_nick: "goat\0" as *const _ as *const _,
17//     },
18fn gen_enum_values(
19    enum_name: &syn::Ident,
20    enum_variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
21) -> (TokenStream, usize) {
22    let crate_ident = crate_ident_new();
23
24    // starts at one as GEnumValue array is null-terminated.
25    let mut n = 1;
26    let recurse = enum_variants.iter().map(|v| {
27        let name = &v.ident;
28        let mut value_name = name.to_string().to_upper_camel_case();
29        let mut value_nick = name.to_string().to_kebab_case();
30
31        let mut name_attr = NestedMetaItem::<syn::LitStr>::new("name").value_required();
32        let mut nick = NestedMetaItem::<syn::LitStr>::new("nick").value_required();
33
34        let found =
35            parse_nested_meta_items(&v.attrs, "enum_value", &mut [&mut name_attr, &mut nick]);
36        if let Err(e) = found {
37            return e.to_compile_error();
38        }
39
40        value_name = name_attr.value.map(|s| s.value()).unwrap_or(value_name);
41        value_nick = nick.value.map(|s| s.value()).unwrap_or(value_nick);
42
43        let value_name = format!("{value_name}\0");
44        let value_nick = format!("{value_nick}\0");
45
46        n += 1;
47        // generates a glib::gobject_ffi::GEnumValue.
48        quote_spanned! {syn::spanned::Spanned::span(&v)=>
49            #crate_ident::gobject_ffi::GEnumValue {
50                value: #enum_name::#name as i32,
51                value_name: #value_name as *const _ as *const _,
52                value_nick: #value_nick as *const _ as *const _,
53            },
54        }
55    });
56    (
57        quote! {
58            #(#recurse)*
59        },
60        n,
61    )
62}
63
64pub fn impl_enum(input: &syn::DeriveInput) -> syn::Result<TokenStream> {
65    let name = &input.ident;
66
67    let enum_variants = match input.data {
68        syn::Data::Enum(ref e) => &e.variants,
69        _ => {
70            return Err(syn::Error::new_spanned(
71                input,
72                "#[derive(glib::Enum)] only supports enums",
73            ))
74        }
75    };
76    let (g_enum_values, nb_enum_values) = gen_enum_values(name, enum_variants);
77
78    let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name")
79        .required()
80        .value_required();
81    let mut allow_name_conflict =
82        NestedMetaItem::<syn::LitBool>::new("allow_name_conflict").value_optional();
83    let found = parse_nested_meta_items(
84        &input.attrs,
85        "enum_type",
86        &mut [&mut gtype_name, &mut allow_name_conflict],
87    )?;
88
89    if found.is_none() {
90        return Err(syn::Error::new_spanned(
91            input,
92            "#[derive(glib::Enum)] requires #[enum_type(name = \"EnumTypeName\")]",
93        ));
94    }
95    let gtype_name = gtype_name.value.unwrap();
96    let allow_name_conflict = allow_name_conflict.found
97        || allow_name_conflict
98            .value
99            .map(|b| b.value())
100            .unwrap_or(false);
101
102    let mut plugin_type = NestedMetaItem::<syn::Path>::new("plugin_type").value_required();
103    let mut lazy_registration =
104        NestedMetaItem::<syn::LitBool>::new("lazy_registration").value_required();
105
106    let found = parse_optional_nested_meta_items(
107        &input.attrs,
108        "enum_dynamic",
109        &mut [&mut plugin_type, &mut lazy_registration],
110    )?;
111
112    let crate_ident = crate_ident_new();
113
114    let register_enum = match found {
115        None => register_enum_as_static(
116            &crate_ident,
117            name,
118            gtype_name,
119            allow_name_conflict,
120            g_enum_values,
121            nb_enum_values,
122        ),
123        Some(_) => {
124            if allow_name_conflict {
125                return Err(syn::Error::new_spanned(
126                    input,
127                    "#[enum_dynamic] and #[enum_type(allow_name_conflict)] are not allowed together",
128                ));
129            }
130
131            let plugin_ty = plugin_type
132                .value
133                .map(|p| p.into_token_stream())
134                .unwrap_or(quote!(#crate_ident::TypeModule));
135            let lazy_registration = lazy_registration.value.map(|b| b.value).unwrap_or_default();
136            register_enum_as_dynamic(
137                &crate_ident,
138                plugin_ty,
139                lazy_registration,
140                name,
141                gtype_name,
142                g_enum_values,
143                nb_enum_values,
144            )
145        }
146    };
147
148    let from_glib = gen_enum_from_glib(name, enum_variants);
149
150    Ok(quote! {
151        impl #crate_ident::translate::IntoGlib for #name {
152            type GlibType = i32;
153
154            #[inline]
155            fn into_glib(self) -> i32 {
156                self as i32
157            }
158        }
159
160        impl #crate_ident::translate::TryFromGlib<i32> for #name {
161            type Error = i32;
162
163            #[inline]
164            unsafe fn try_from_glib(value: i32) -> ::core::result::Result<Self, i32> {
165                let from_glib = || {
166                    #from_glib
167                };
168
169                from_glib().ok_or(value)
170            }
171        }
172
173        impl #crate_ident::translate::FromGlib<i32> for #name {
174            #[inline]
175            unsafe fn from_glib(value: i32) -> Self {
176                use #crate_ident::translate::TryFromGlib;
177
178                Self::try_from_glib(value).unwrap()
179            }
180        }
181
182        impl #crate_ident::value::ValueType for #name {
183            type Type = Self;
184        }
185
186        unsafe impl<'a> #crate_ident::value::FromValue<'a> for #name {
187            type Checker = #crate_ident::value::GenericValueTypeChecker<Self>;
188
189            #[inline]
190            unsafe fn from_value(value: &'a #crate_ident::value::Value) -> Self {
191                #crate_ident::translate::from_glib(#crate_ident::gobject_ffi::g_value_get_enum(
192                    #crate_ident::translate::ToGlibPtr::to_glib_none(value).0
193                ))
194            }
195        }
196
197        impl #crate_ident::prelude::ToValue for #name {
198            #[inline]
199            fn to_value(&self) -> #crate_ident::value::Value {
200                let mut value = #crate_ident::value::Value::for_value_type::<Self>();
201                unsafe {
202                    #crate_ident::gobject_ffi::g_value_set_enum(
203                        #crate_ident::translate::ToGlibPtrMut::to_glib_none_mut(&mut value).0,
204                        #crate_ident::translate::IntoGlib::into_glib(*self)
205                    )
206                }
207                value
208            }
209
210            #[inline]
211            fn value_type(&self) -> #crate_ident::Type {
212                <Self as #crate_ident::prelude::StaticType>::static_type()
213            }
214        }
215
216        impl ::std::convert::From<#name> for #crate_ident::Value {
217            #[inline]
218            fn from(v: #name) -> Self {
219                #crate_ident::value::ToValue::to_value(&v)
220            }
221        }
222
223        impl #crate_ident::prelude::StaticType for #name {
224            #[inline]
225            fn static_type() -> #crate_ident::Type {
226                Self::register_enum()
227            }
228        }
229
230        #register_enum
231
232        impl #crate_ident::HasParamSpec for #name {
233            type ParamSpec = #crate_ident::ParamSpecEnum;
234            type SetValue = Self;
235            type BuilderFn = fn(&::core::primitive::str, Self) -> #crate_ident::ParamSpecEnumBuilder<Self>;
236
237            fn param_spec_builder() -> Self::BuilderFn {
238                |name, default_value| Self::ParamSpec::builder_with_default(name, default_value)
239            }
240        }
241    })
242}
243
244// Registers the enum as a static type.
245fn register_enum_as_static(
246    crate_ident: &TokenStream,
247    name: &syn::Ident,
248    gtype_name: syn::LitStr,
249    allow_name_conflict: bool,
250    g_enum_values: TokenStream,
251    nb_enum_values: usize,
252) -> TokenStream {
253    let type_name_snippet = if allow_name_conflict {
254        quote! {
255            unsafe {
256                let mut i = 0;
257                loop {
258                    let type_name = ::std::ffi::CString::new(if i == 0 {
259                        #gtype_name
260                    } else {
261                        format!("{}-{}", #gtype_name, i)
262                    })
263                    .unwrap();
264                    if #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()) == #crate_ident::gobject_ffi::G_TYPE_INVALID
265                    {
266                        break type_name;
267                    }
268                    i += 1;
269                }
270            }
271        }
272    } else {
273        quote! {
274            unsafe {
275                let type_name = ::std::ffi::CString::new(#gtype_name).unwrap();
276                assert_eq!(
277                    #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()),
278                    #crate_ident::gobject_ffi::G_TYPE_INVALID,
279                    "Type {} has already been registered",
280                    type_name.to_str().unwrap()
281                );
282
283                type_name
284            }
285        }
286    };
287
288    // registers the enum on first use (lazy registration).
289    quote! {
290        impl #name {
291            /// Registers the enum only once.
292            #[inline]
293            fn register_enum() -> #crate_ident::Type {
294                static TYPE: ::std::sync::OnceLock<#crate_ident::Type> = ::std::sync::OnceLock::new();
295                *TYPE.get_or_init(|| {
296                    static mut VALUES: [#crate_ident::gobject_ffi::GEnumValue; #nb_enum_values] = [
297                        #g_enum_values
298                        #crate_ident::gobject_ffi::GEnumValue {
299                            value: 0,
300                            value_name: ::std::ptr::null(),
301                            value_nick: ::std::ptr::null(),
302                        },
303                    ];
304                    let type_name = #type_name_snippet;
305                    unsafe {
306                        let type_ = #crate_ident::gobject_ffi::g_enum_register_static(type_name.as_ptr(), VALUES.as_ptr());
307                        let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_);
308                        assert!(type_.is_valid());
309                        type_
310                    }
311                })
312            }
313        }
314    }
315}
316
317// The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt`] and [`TypeModuleExt`]).
318// An enum can be reregistered as a dynamic type.
319fn register_enum_as_dynamic(
320    crate_ident: &TokenStream,
321    plugin_ty: TokenStream,
322    lazy_registration: bool,
323    name: &syn::Ident,
324    gtype_name: syn::LitStr,
325    g_enum_values: TokenStream,
326    nb_enum_values: usize,
327) -> TokenStream {
328    // Wrap each GEnumValue to EnumValue
329    let g_enum_values_expr: syn::ExprArray = syn::parse_quote! { [#g_enum_values] };
330    let enum_values_iter = g_enum_values_expr.elems.iter().map(|v| {
331        quote_spanned! {syn::spanned::Spanned::span(&v)=>
332            #crate_ident::EnumValue::unsafe_from(#v),
333        }
334    });
335
336    let enum_values = quote! {
337        #crate_ident::enums::EnumValuesStorage<#nb_enum_values> = unsafe {
338            #crate_ident::enums::EnumValuesStorage::<#nb_enum_values>::new([
339                #(#enum_values_iter)*
340            ])
341        }
342    };
343
344    // The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt`] and [`TypeModuleExt`]).
345    // An enum can be reregistered as a dynamic type.
346    if lazy_registration {
347        // registers the enum as a dynamic type on the first use (lazy registration).
348        // a weak reference on the plugin is stored and will be used later on the first use of the enum.
349        // this implementation relies on a static storage of a weak reference on the plugin and of the GLib type to know if the enum has been registered.
350
351        // the registration status type.
352        let registration_status_type = format_ident!("{}RegistrationStatus", name);
353        // name of the static variable to store the registration status.
354        let registration_status = format_ident!(
355            "{}",
356            registration_status_type.to_string().to_shouty_snake_case()
357        );
358        // name of the static array to store the enumeration values.
359        let enum_values_array = format_ident!("{}_VALUES", name.to_string().to_shouty_snake_case());
360
361        quote! {
362            /// The registration status type: a tuple of the weak reference on the plugin and of the GLib type.
363            struct #registration_status_type(<#plugin_ty as #crate_ident::clone::Downgrade>::Weak, #crate_ident::Type);
364            unsafe impl Send for #registration_status_type {}
365
366            /// The registration status protected by a mutex guarantees so that no other threads are concurrently accessing the data.
367            static #registration_status: ::std::sync::Mutex<Option<#registration_status_type>> = ::std::sync::Mutex::new(None);
368
369            /// Array of `EnumValue` for the possible enumeration values.
370            static #enum_values_array: #enum_values;
371
372            impl #name {
373                /// Registers the enum as a dynamic type within the plugin only once.
374                /// Plugin must have been used at least once.
375                /// Do nothing if plugin has never been used or if the enum is already registered as a dynamic type.
376                #[inline]
377                fn register_enum() -> #crate_ident::Type {
378                    let mut registration_status = #registration_status.lock().unwrap();
379                    match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
380                        // plugin has never been used, so the enum cannot be registered as a dynamic type.
381                        None => #crate_ident::Type::INVALID,
382                        // plugin has been used and the enum has not been registered yet, so registers it as a dynamic type.
383                        Some(#registration_status_type(type_plugin, type_)) if !type_.is_valid() => {
384                            *type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_enum(type_plugin.upgrade().unwrap().as_ref(), #gtype_name, #enum_values_array.as_ref());
385                            *type_
386                        },
387                        // plugin has been used and the enum has already been registered as a dynamic type.
388                        Some(#registration_status_type(_, type_)) => *type_
389                    }
390                }
391
392                /// Depending on the plugin lifecycle state and on the registration status of the enum:
393                /// If plugin is used (and has loaded the implementation) for the first time, postpones the registration and stores a weak reference on the plugin.
394                /// If plugin is reused (and has reloaded the implementation) and the enum has been already registered as a dynamic type, reregisters it.
395                /// An enum can be reregistered several times as a dynamic type.
396                /// If plugin is reused (and has reloaded the implementation) and the enum has not been registered yet as a dynamic type, do nothing.
397                #[inline]
398                pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
399                    let mut registration_status = #registration_status.lock().unwrap();
400                    match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
401                        // plugin has never been used (this is the first time), so postpones registration of the enum as a dynamic type on the first use.
402                        None => {
403                            *registration_status = Some(#registration_status_type(#crate_ident::clone::Downgrade::downgrade(type_plugin), #crate_ident::Type::INVALID));
404                            true
405                        },
406                        // plugin has been used at least one time and the enum has been registered as a dynamic type at least one time, so re-registers it.
407                        Some(#registration_status_type(_, type_)) if type_.is_valid() => {
408                            *type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_enum(type_plugin, #gtype_name, #enum_values_array.as_ref());
409                            type_.is_valid()
410                        },
411                        // plugin has been used at least one time but the enum has not been registered yet as a dynamic type, so keeps postponed registration.
412                        Some(_) => {
413                            true
414                        }
415                    }
416                }
417
418                /// Depending on the plugin lifecycle state and on the registration status of the enum:
419                /// If plugin has been used (or reused) but the enum has not been registered yet as a dynamic type, cancels the postponed registration by deleting the weak reference on the plugin.
420                /// Else do nothing.
421                #[inline]
422                pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
423                    let mut registration_status = #registration_status.lock().unwrap();
424                    match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
425                        // plugin has never been used, so unload implementation is unexpected.
426                        None => false,
427                        // plugin has been used at least one time and the enum has been registered as a dynamic type at least one time.
428                        Some(#registration_status_type(_, type_)) if type_.is_valid() => true,
429                        // plugin has been used at least one time but the enum has not been registered yet as a dynamic type, so cancels the postponed registration.
430                        Some(_) => {
431                            *registration_status = None;
432                            true
433                        }
434                    }
435                }
436            }
437        }
438    } else {
439        // registers immediately the enum as a dynamic type.
440
441        // name of the static variable to store the GLib type.
442        let gtype_status = format_ident!("{}_G_TYPE", name.to_string().to_shouty_snake_case());
443
444        quote! {
445            /// The GLib type which can be safely shared between threads.
446            static #gtype_status: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(#crate_ident::gobject_ffi::G_TYPE_INVALID);
447
448            impl #name {
449                /// Do nothing as the enum has been registered on implementation load.
450                #[inline]
451                fn register_enum() -> #crate_ident::Type {
452                    let gtype = #gtype_status.load(::std::sync::atomic::Ordering::Acquire);
453                    unsafe { <#crate_ident::Type as #crate_ident::translate::FromGlib<#crate_ident::ffi::GType>>::from_glib(gtype) }
454                }
455
456                /// Registers the enum as a dynamic type within the plugin.
457                /// The enum can be registered several times as a dynamic type.
458                #[inline]
459                pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
460                    static VALUES: #enum_values;
461                    let gtype = #crate_ident::translate::IntoGlib::into_glib(<#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_enum(type_plugin, #gtype_name, VALUES.as_ref()));
462                    #gtype_status.store(gtype, ::std::sync::atomic::Ordering::Release);
463                    gtype != #crate_ident::gobject_ffi::G_TYPE_INVALID
464                }
465
466                /// Do nothing as enums registered as dynamic types are never unregistered.
467                #[inline]
468                pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
469                    true
470                }
471            }
472        }
473    }
474}