1use 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
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 #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
244fn 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 quote! {
290 impl #name {
291 #[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
317fn 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 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 if lazy_registration {
347 let registration_status_type = format_ident!("{}RegistrationStatus", name);
353 let registration_status = format_ident!(
355 "{}",
356 registration_status_type.to_string().to_shouty_snake_case()
357 );
358 let enum_values_array = format_ident!("{}_VALUES", name.to_string().to_shouty_snake_case());
360
361 quote! {
362 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 static #registration_status: ::std::sync::Mutex<Option<#registration_status_type>> = ::std::sync::Mutex::new(None);
368
369 static #enum_values_array: #enum_values;
371
372 impl #name {
373 #[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 None => #crate_ident::Type::INVALID,
382 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 Some(#registration_status_type(_, type_)) => *type_
389 }
390 }
391
392 #[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 None => {
403 *registration_status = Some(#registration_status_type(#crate_ident::clone::Downgrade::downgrade(type_plugin), #crate_ident::Type::INVALID));
404 true
405 },
406 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 Some(_) => {
413 true
414 }
415 }
416 }
417
418 #[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 None => false,
427 Some(#registration_status_type(_, type_)) if type_.is_valid() => true,
429 Some(_) => {
431 *registration_status = None;
432 true
433 }
434 }
435 }
436 }
437 }
438 } else {
439 let gtype_status = format_ident!("{}_G_TYPE", name.to_string().to_shouty_snake_case());
443
444 quote! {
445 static #gtype_status: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(#crate_ident::gobject_ffi::G_TYPE_INVALID);
447
448 impl #name {
449 #[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 #[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 #[inline]
468 pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
469 true
470 }
471 }
472 }
473 }
474}