Skip to main content

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::{ToTokens, format_ident, quote, quote_spanned};
6
7use crate::utils::{
8    NestedMetaItem, crate_ident_new, gen_enum_from_glib, parse_nested_meta_items,
9    parse_optional_nested_meta_items,
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 ::core::convert::TryFrom<i32> for #name {
161            type Error = i32;
162
163            #[inline]
164            fn try_from(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::TryFromGlib<i32> for #name {
174            type Error = i32;
175
176            #[inline]
177            unsafe fn try_from_glib(value: i32) -> ::core::result::Result<Self, i32> {
178                <Self as ::core::convert::TryFrom<i32>>::try_from(value)
179            }
180        }
181
182        impl #crate_ident::translate::FromGlib<i32> for #name {
183            #[inline]
184            unsafe fn from_glib(value: i32) -> Self {
185                use #crate_ident::translate::TryFromGlib;
186
187                Self::try_from_glib(value).unwrap()
188            }
189        }
190
191        impl #crate_ident::value::ValueType for #name {
192            type Type = Self;
193        }
194
195        unsafe impl<'a> #crate_ident::value::FromValue<'a> for #name {
196            type Checker = #crate_ident::value::GenericValueTypeChecker<Self>;
197
198            #[inline]
199            unsafe fn from_value(value: &'a #crate_ident::value::Value) -> Self {
200                #crate_ident::translate::from_glib(#crate_ident::gobject_ffi::g_value_get_enum(
201                    #crate_ident::translate::ToGlibPtr::to_glib_none(value).0
202                ))
203            }
204        }
205
206        impl #crate_ident::prelude::ToValue for #name {
207            #[inline]
208            fn to_value(&self) -> #crate_ident::value::Value {
209                let mut value = #crate_ident::value::Value::for_value_type::<Self>();
210                unsafe {
211                    #crate_ident::gobject_ffi::g_value_set_enum(
212                        #crate_ident::translate::ToGlibPtrMut::to_glib_none_mut(&mut value).0,
213                        #crate_ident::translate::IntoGlib::into_glib(*self)
214                    )
215                }
216                value
217            }
218
219            #[inline]
220            fn value_type(&self) -> #crate_ident::Type {
221                <Self as #crate_ident::prelude::StaticType>::static_type()
222            }
223        }
224
225        impl ::std::convert::From<#name> for #crate_ident::Value {
226            #[inline]
227            fn from(v: #name) -> Self {
228                #crate_ident::value::ToValue::to_value(&v)
229            }
230        }
231
232        impl #crate_ident::prelude::StaticType for #name {
233            #[inline]
234            fn static_type() -> #crate_ident::Type {
235                Self::register_enum()
236            }
237        }
238
239        #register_enum
240
241        impl #crate_ident::HasParamSpec for #name {
242            type ParamSpec = #crate_ident::ParamSpecEnum;
243            type SetValue = Self;
244            type BuilderFn = fn(&::core::primitive::str, Self) -> #crate_ident::ParamSpecEnumBuilder<Self>;
245
246            fn param_spec_builder() -> Self::BuilderFn {
247                |name, default_value| Self::ParamSpec::builder_with_default(name, default_value)
248            }
249        }
250    })
251}
252
253// Registers the enum as a static type.
254fn register_enum_as_static(
255    crate_ident: &TokenStream,
256    name: &syn::Ident,
257    gtype_name: syn::LitStr,
258    allow_name_conflict: bool,
259    g_enum_values: TokenStream,
260    nb_enum_values: usize,
261) -> TokenStream {
262    let type_name_snippet = if allow_name_conflict {
263        quote! {
264            unsafe {
265                let mut i = 0;
266                loop {
267                    let type_name = ::std::ffi::CString::new(if i == 0 {
268                        #gtype_name
269                    } else {
270                        format!("{}-{}", #gtype_name, i)
271                    })
272                    .unwrap();
273                    if #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()) == #crate_ident::gobject_ffi::G_TYPE_INVALID
274                    {
275                        break type_name;
276                    }
277                    i += 1;
278                }
279            }
280        }
281    } else {
282        quote! {
283            unsafe {
284                let type_name = ::std::ffi::CString::new(#gtype_name).unwrap();
285                assert_eq!(
286                    #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()),
287                    #crate_ident::gobject_ffi::G_TYPE_INVALID,
288                    "Type {} has already been registered",
289                    type_name.to_str().unwrap()
290                );
291
292                type_name
293            }
294        }
295    };
296
297    // registers the enum on first use (lazy registration).
298    quote! {
299        impl #name {
300            /// Registers the enum only once.
301            #[inline]
302            fn register_enum() -> #crate_ident::Type {
303                static TYPE: ::std::sync::OnceLock<#crate_ident::Type> = ::std::sync::OnceLock::new();
304                *TYPE.get_or_init(|| {
305                    static mut VALUES: [#crate_ident::gobject_ffi::GEnumValue; #nb_enum_values] = [
306                        #g_enum_values
307                        #crate_ident::gobject_ffi::GEnumValue {
308                            value: 0,
309                            value_name: ::std::ptr::null(),
310                            value_nick: ::std::ptr::null(),
311                        },
312                    ];
313                    let type_name = #type_name_snippet;
314                    unsafe {
315                        let type_ = #crate_ident::gobject_ffi::g_enum_register_static(type_name.as_ptr(), VALUES.as_ptr());
316                        let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_);
317                        assert!(type_.is_valid());
318                        type_
319                    }
320                })
321            }
322        }
323    }
324}
325
326// The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt`] and [`TypeModuleExt`]).
327// An enum can be reregistered as a dynamic type.
328fn register_enum_as_dynamic(
329    crate_ident: &TokenStream,
330    plugin_ty: TokenStream,
331    lazy_registration: bool,
332    name: &syn::Ident,
333    gtype_name: syn::LitStr,
334    g_enum_values: TokenStream,
335    nb_enum_values: usize,
336) -> TokenStream {
337    // Wrap each GEnumValue to EnumValue
338    let g_enum_values_expr: syn::ExprArray = syn::parse_quote! { [#g_enum_values] };
339    let enum_values_iter = g_enum_values_expr.elems.iter().map(|v| {
340        quote_spanned! {syn::spanned::Spanned::span(&v)=>
341            #crate_ident::EnumValue::unsafe_from(#v),
342        }
343    });
344
345    let enum_values = quote! {
346        #crate_ident::enums::EnumValuesStorage<#nb_enum_values> = unsafe {
347            #crate_ident::enums::EnumValuesStorage::<#nb_enum_values>::new([
348                #(#enum_values_iter)*
349            ])
350        }
351    };
352
353    // The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt`] and [`TypeModuleExt`]).
354    // An enum can be reregistered as a dynamic type.
355    if lazy_registration {
356        // registers the enum as a dynamic type on the first use (lazy registration).
357        // a weak reference on the plugin is stored and will be used later on the first use of the enum.
358        // 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.
359
360        // the registration status type.
361        let registration_status_type = format_ident!("{}RegistrationStatus", name);
362        // name of the static variable to store the registration status.
363        let registration_status = format_ident!(
364            "{}",
365            registration_status_type.to_string().to_shouty_snake_case()
366        );
367        // name of the static array to store the enumeration values.
368        let enum_values_array = format_ident!("{}_VALUES", name.to_string().to_shouty_snake_case());
369
370        quote! {
371            /// The registration status type: a tuple of the weak reference on the plugin and of the GLib type.
372            struct #registration_status_type(<#plugin_ty as #crate_ident::clone::Downgrade>::Weak, #crate_ident::Type);
373            unsafe impl Send for #registration_status_type {}
374
375            /// The registration status protected by a mutex guarantees so that no other threads are concurrently accessing the data.
376            static #registration_status: ::std::sync::Mutex<Option<#registration_status_type>> = ::std::sync::Mutex::new(None);
377
378            /// Array of `EnumValue` for the possible enumeration values.
379            static #enum_values_array: #enum_values;
380
381            impl #name {
382                /// Registers the enum as a dynamic type within the plugin only once.
383                /// Plugin must have been used at least once.
384                /// Do nothing if plugin has never been used or if the enum is already registered as a dynamic type.
385                #[inline]
386                fn register_enum() -> #crate_ident::Type {
387                    let mut registration_status = #registration_status.lock().unwrap();
388                    match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
389                        // plugin has never been used, so the enum cannot be registered as a dynamic type.
390                        None => #crate_ident::Type::INVALID,
391                        // plugin has been used and the enum has not been registered yet, so registers it as a dynamic type.
392                        Some(#registration_status_type(type_plugin, type_)) if !type_.is_valid() => {
393                            *type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_enum(type_plugin.upgrade().unwrap().as_ref(), #gtype_name, #enum_values_array.as_ref());
394                            *type_
395                        },
396                        // plugin has been used and the enum has already been registered as a dynamic type.
397                        Some(#registration_status_type(_, type_)) => *type_
398                    }
399                }
400
401                /// Depending on the plugin lifecycle state and on the registration status of the enum:
402                /// If plugin is used (and has loaded the implementation) for the first time, postpones the registration and stores a weak reference on the plugin.
403                /// If plugin is reused (and has reloaded the implementation) and the enum has been already registered as a dynamic type, reregisters it.
404                /// An enum can be reregistered several times as a dynamic type.
405                /// If plugin is reused (and has reloaded the implementation) and the enum has not been registered yet as a dynamic type, do nothing.
406                #[inline]
407                pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
408                    let mut registration_status = #registration_status.lock().unwrap();
409                    match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
410                        // plugin has never been used (this is the first time), so postpones registration of the enum as a dynamic type on the first use.
411                        None => {
412                            *registration_status = Some(#registration_status_type(#crate_ident::clone::Downgrade::downgrade(type_plugin), #crate_ident::Type::INVALID));
413                            true
414                        },
415                        // 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.
416                        Some(#registration_status_type(_, type_)) if type_.is_valid() => {
417                            *type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_enum(type_plugin, #gtype_name, #enum_values_array.as_ref());
418                            type_.is_valid()
419                        },
420                        // plugin has been used at least one time but the enum has not been registered yet as a dynamic type, so keeps postponed registration.
421                        Some(_) => {
422                            true
423                        }
424                    }
425                }
426
427                /// Depending on the plugin lifecycle state and on the registration status of the enum:
428                /// 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.
429                /// Else do nothing.
430                #[inline]
431                pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
432                    let mut registration_status = #registration_status.lock().unwrap();
433                    match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
434                        // plugin has never been used, so unload implementation is unexpected.
435                        None => false,
436                        // plugin has been used at least one time and the enum has been registered as a dynamic type at least one time.
437                        Some(#registration_status_type(_, type_)) if type_.is_valid() => true,
438                        // 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.
439                        Some(_) => {
440                            *registration_status = None;
441                            true
442                        }
443                    }
444                }
445            }
446        }
447    } else {
448        // registers immediately the enum as a dynamic type.
449
450        // name of the static variable to store the GLib type.
451        let gtype_status = format_ident!("{}_G_TYPE", name.to_string().to_shouty_snake_case());
452
453        quote! {
454            /// The GLib type which can be safely shared between threads.
455            static #gtype_status: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(#crate_ident::gobject_ffi::G_TYPE_INVALID);
456
457            impl #name {
458                /// Do nothing as the enum has been registered on implementation load.
459                #[inline]
460                fn register_enum() -> #crate_ident::Type {
461                    let gtype = #gtype_status.load(::std::sync::atomic::Ordering::Acquire);
462                    unsafe { <#crate_ident::Type as #crate_ident::translate::FromGlib<#crate_ident::ffi::GType>>::from_glib(gtype) }
463                }
464
465                /// Registers the enum as a dynamic type within the plugin.
466                /// The enum can be registered several times as a dynamic type.
467                #[inline]
468                pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
469                    static VALUES: #enum_values;
470                    let gtype = #crate_ident::translate::IntoGlib::into_glib(<#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_enum(type_plugin, #gtype_name, VALUES.as_ref()));
471                    #gtype_status.store(gtype, ::std::sync::atomic::Ordering::Release);
472                    gtype != #crate_ident::gobject_ffi::G_TYPE_INVALID
473                }
474
475                /// Do nothing as enums registered as dynamic types are never unregistered.
476                #[inline]
477                pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
478                    true
479                }
480            }
481        }
482    }
483}