1use 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
12fn 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 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 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
253fn 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 quote! {
299 impl #name {
300 #[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
326fn 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 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 if lazy_registration {
356 let registration_status_type = format_ident!("{}RegistrationStatus", name);
362 let registration_status = format_ident!(
364 "{}",
365 registration_status_type.to_string().to_shouty_snake_case()
366 );
367 let enum_values_array = format_ident!("{}_VALUES", name.to_string().to_shouty_snake_case());
369
370 quote! {
371 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 static #registration_status: ::std::sync::Mutex<Option<#registration_status_type>> = ::std::sync::Mutex::new(None);
377
378 static #enum_values_array: #enum_values;
380
381 impl #name {
382 #[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 None => #crate_ident::Type::INVALID,
391 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 Some(#registration_status_type(_, type_)) => *type_
398 }
399 }
400
401 #[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 None => {
412 *registration_status = Some(#registration_status_type(#crate_ident::clone::Downgrade::downgrade(type_plugin), #crate_ident::Type::INVALID));
413 true
414 },
415 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 Some(_) => {
422 true
423 }
424 }
425 }
426
427 #[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 None => false,
436 Some(#registration_status_type(_, type_)) if type_.is_valid() => true,
438 Some(_) => {
440 *registration_status = None;
441 true
442 }
443 }
444 }
445 }
446 }
447 } else {
448 let gtype_status = format_ident!("{}_G_TYPE", name.to_string().to_shouty_snake_case());
452
453 quote! {
454 static #gtype_status: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(#crate_ident::gobject_ffi::G_TYPE_INVALID);
456
457 impl #name {
458 #[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 #[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 #[inline]
477 pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
478 true
479 }
480 }
481 }
482 }
483}