1use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase};
4use proc_macro2::TokenStream;
5use quote::{format_ident, quote, quote_spanned, ToTokens};
6use syn::{
7 punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Ident, Variant, Visibility,
8};
9
10use crate::utils::{
11 crate_ident_new, parse_nested_meta_items, parse_optional_nested_meta_items, NestedMetaItem,
12};
13
14pub const WRONG_PLACE_MSG: &str = "#[glib::flags] only supports enums";
15
16pub struct AttrInput {
17 pub enum_name: syn::LitStr,
18 pub allow_name_conflict: bool,
19}
20struct FlagsDesc {
21 variant: Variant,
22 name: Option<String>,
23 nick: Option<String>,
24 skip: bool,
25}
26impl FlagsDesc {
27 fn from_attrs(variant: Variant, attrs: &[Attribute]) -> syn::Result<Self> {
28 let mut name = NestedMetaItem::<syn::LitStr>::new("name").value_required();
29 let mut nick = NestedMetaItem::<syn::LitStr>::new("nick").value_required();
30 let mut skip = NestedMetaItem::<syn::LitBool>::new("skip").value_optional();
31
32 parse_nested_meta_items(attrs, "flags_value", &mut [&mut name, &mut nick, &mut skip])?;
33
34 Ok(Self {
35 variant,
36 name: name.value.map(|s| s.value()),
37 nick: nick.value.map(|s| s.value()),
38 skip: skip.found || skip.value.map(|b| b.value()).unwrap_or(false),
39 })
40 }
41}
42
43fn gen_flags_values(
50 enum_name: &Ident,
51 enum_variants: &Punctuated<Variant, Comma>,
52) -> (TokenStream, usize) {
53 let crate_ident = crate_ident_new();
54
55 let mut n = 1;
57 let recurse = enum_variants
58 .iter()
59 .map(|v| FlagsDesc::from_attrs(v.clone(), &v.attrs).unwrap())
60 .filter(|desc| !desc.skip)
61 .map(|desc| {
62 let v = desc.variant;
63 let name = &v.ident;
64 let mut value_name = name.to_string().to_upper_camel_case();
65 let mut value_nick = name.to_string().to_kebab_case();
66
67 if let Some(n) = desc.name {
68 value_name = n;
69 }
70 if let Some(n) = desc.nick {
71 value_nick = n;
72 }
73
74 let value_name = format!("{value_name}\0");
75 let value_nick = format!("{value_nick}\0");
76
77 n += 1;
78 quote_spanned! {v.span()=>
79 #crate_ident::gobject_ffi::GFlagsValue {
80 value: #enum_name::#name.bits(),
81 value_name: #value_name as *const _ as *const _,
82 value_nick: #value_nick as *const _ as *const _,
83 },
84 }
85 });
86 (
87 quote! {
88 #(#recurse)*
89 },
90 n,
91 )
92}
93
94fn gen_bitflags(
95 enum_name: &Ident,
96 visibility: &Visibility,
97 enum_variants: &Punctuated<Variant, Comma>,
98 crate_ident: &TokenStream,
99) -> TokenStream {
100 let recurse = enum_variants.iter().map(|v| {
101 let name = &v.ident;
102 let disc = v.discriminant.as_ref().expect("missing discriminant");
103 let value = &disc.1;
104
105 quote_spanned! {v.span()=>
106 const #name = #value;
107 }
108 });
109
110 quote! {
111 #crate_ident::bitflags::bitflags! {
112 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
113 #visibility struct #enum_name: u32 {
114 #(#recurse)*
115 }
116 }
117 }
118}
119
120fn gen_default(
121 enum_name: &Ident,
122 enum_variants: &Punctuated<Variant, Comma>,
123) -> Option<TokenStream> {
124 enum_variants
125 .iter()
126 .find(|v| v.attrs.iter().any(|attr| attr.path().is_ident("default")))
127 .map(|v| {
128 let default_value = &v.ident;
129
130 quote! {
131 impl Default for #enum_name {
132 fn default() -> Self {
133 Self::from_bits_retain(#enum_name::#default_value.bits())
134 }
135 }
136 }
137 })
138}
139
140pub fn impl_flags(attr_meta: AttrInput, input: &mut syn::ItemEnum) -> TokenStream {
141 let gtype_name = attr_meta.enum_name;
142
143 let syn::ItemEnum {
144 attrs,
145 ident: name,
146 vis: visibility,
147 ..
148 } = input;
149
150 let enum_variants = &input.variants;
151 let (g_flags_values, nb_flags_values) = gen_flags_values(name, enum_variants);
152
153 let crate_ident = crate_ident_new();
154
155 let mut plugin_type = NestedMetaItem::<syn::Path>::new("plugin_type").value_required();
156 let mut lazy_registration =
157 NestedMetaItem::<syn::LitBool>::new("lazy_registration").value_required();
158
159 let found = parse_optional_nested_meta_items(
160 &*attrs,
161 "flags_dynamic",
162 &mut [&mut plugin_type, &mut lazy_registration],
163 );
164
165 let register_flags = match found {
166 Err(e) => return e.to_compile_error(),
167 Ok(None) => register_flags_as_static(
168 &crate_ident,
169 name,
170 gtype_name,
171 attr_meta.allow_name_conflict,
172 g_flags_values,
173 nb_flags_values,
174 ),
175 Ok(Some(_)) => {
176 if attr_meta.allow_name_conflict {
177 return syn::Error::new_spanned(
178 input,
179 "#[flags_dynamic] and #[glib::flags(allow_name_conflict)] are not allowed together",
180 ).to_compile_error();
181 }
182
183 attrs.retain(|attr| !attr.path().is_ident("flags_dynamic"));
185 let plugin_ty = plugin_type
186 .value
187 .map(|p| p.into_token_stream())
188 .unwrap_or(quote!(#crate_ident::TypeModule));
189 let lazy_registration = lazy_registration.value.map(|b| b.value).unwrap_or_default();
190 register_flags_as_dynamic(
191 &crate_ident,
192 plugin_ty,
193 lazy_registration,
194 name,
195 gtype_name,
196 g_flags_values,
197 nb_flags_values,
198 )
199 }
200 };
201
202 let bitflags = gen_bitflags(name, visibility, enum_variants, &crate_ident);
203 let default_impl = gen_default(name, enum_variants);
204
205 quote! {
206 #bitflags
207
208 #default_impl
209
210 impl #crate_ident::translate::IntoGlib for #name {
211 type GlibType = u32;
212
213 #[inline]
214 fn into_glib(self) -> u32 {
215 self.bits()
216 }
217 }
218
219 impl #crate_ident::translate::FromGlib<u32> for #name {
220 #[inline]
221 unsafe fn from_glib(value: u32) -> Self {
222 Self::from_bits_truncate(value)
223 }
224 }
225
226 impl #crate_ident::value::ValueType for #name {
227 type Type = Self;
228 }
229
230 unsafe impl<'a> #crate_ident::value::FromValue<'a> for #name {
231 type Checker = #crate_ident::value::GenericValueTypeChecker<Self>;
232
233 #[inline]
234 unsafe fn from_value(value: &'a #crate_ident::value::Value) -> Self {
235 #crate_ident::translate::from_glib(#crate_ident::gobject_ffi::g_value_get_flags(
236 #crate_ident::translate::ToGlibPtr::to_glib_none(value).0
237 ))
238 }
239 }
240
241 impl #crate_ident::value::ToValue for #name {
242 #[inline]
243 fn to_value(&self) -> #crate_ident::value::Value {
244 let mut value = #crate_ident::value::Value::for_value_type::<Self>();
245 unsafe {
246 #crate_ident::gobject_ffi::g_value_set_flags(
247 #crate_ident::translate::ToGlibPtrMut::to_glib_none_mut(&mut value).0,
248 #crate_ident::translate::IntoGlib::into_glib(*self)
249 )
250 }
251 value
252 }
253
254 #[inline]
255 fn value_type(&self) -> #crate_ident::Type {
256 <Self as #crate_ident::prelude::StaticType>::static_type()
257 }
258 }
259
260 impl #crate_ident::HasParamSpec for #name {
261 type ParamSpec = #crate_ident::ParamSpecFlags;
262 type SetValue = Self;
263 type BuilderFn = fn(&::core::primitive::str) -> #crate_ident::ParamSpecFlagsBuilder<Self>;
264
265 fn param_spec_builder() -> Self::BuilderFn {
266 |name| Self::ParamSpec::builder(name)
267 }
268 }
269
270 impl ::std::convert::From<#name> for #crate_ident::Value {
271 #[inline]
272 fn from(v: #name) -> Self {
273 #crate_ident::value::ToValue::to_value(&v)
274 }
275 }
276
277 impl #crate_ident::prelude::StaticType for #name {
278 #[inline]
279 fn static_type() -> #crate_ident::Type {
280 Self::register_flags()
281 }
282 }
283
284 #register_flags
285 }
286}
287
288fn register_flags_as_static(
290 crate_ident: &TokenStream,
291 name: &syn::Ident,
292 gtype_name: syn::LitStr,
293 allow_name_conflict: bool,
294 g_flags_values: TokenStream,
295 nb_flags_values: usize,
296) -> TokenStream {
297 let type_name_snippet = if allow_name_conflict {
298 quote! {
299 unsafe {
300 let mut i = 0;
301 loop {
302 let type_name = ::std::ffi::CString::new(if i == 0 {
303 #gtype_name
304 } else {
305 format!("{}-{}", #gtype_name, i)
306 })
307 .unwrap();
308 if #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()) == #crate_ident::gobject_ffi::G_TYPE_INVALID
309 {
310 break type_name;
311 }
312 i += 1;
313 }
314 }
315 }
316 } else {
317 quote! {
318 unsafe {
319 let type_name = ::std::ffi::CString::new(#gtype_name).unwrap();
320 assert_eq!(
321 #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()),
322 #crate_ident::gobject_ffi::G_TYPE_INVALID,
323 "Type {} has already been registered",
324 type_name.to_str().unwrap()
325 );
326
327 type_name
328 }
329 }
330 };
331
332 quote! {
334 impl #name {
335 #[inline]
337 fn register_flags() -> #crate_ident::Type {
338 static TYPE: ::std::sync::OnceLock<#crate_ident::Type> = ::std::sync::OnceLock::new();
339 *TYPE.get_or_init(|| {
340 static mut VALUES: [#crate_ident::gobject_ffi::GFlagsValue; #nb_flags_values] = [
341 #g_flags_values
342 #crate_ident::gobject_ffi::GFlagsValue {
343 value: 0,
344 value_name: ::std::ptr::null(),
345 value_nick: ::std::ptr::null(),
346 },
347 ];
348
349 let type_name = #type_name_snippet;
350 unsafe {
351 let type_ = #crate_ident::gobject_ffi::g_flags_register_static(type_name.as_ptr(), VALUES.as_ptr());
352 let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_);
353 assert!(type_.is_valid());
354 type_
355 }
356 })
357 }
358 }
359 }
360}
361
362fn register_flags_as_dynamic(
365 crate_ident: &TokenStream,
366 plugin_ty: TokenStream,
367 lazy_registration: bool,
368 name: &syn::Ident,
369 gtype_name: syn::LitStr,
370 g_flags_values: TokenStream,
371 nb_flags_values: usize,
372) -> TokenStream {
373 let g_flags_values_expr: syn::ExprArray = syn::parse_quote! { [#g_flags_values] };
375 let flags_values_iter = g_flags_values_expr.elems.iter().map(|v| {
376 quote_spanned! {syn::spanned::Spanned::span(&v)=>
377 #crate_ident::FlagsValue::unsafe_from(#v),
378 }
379 });
380
381 let flags_values = quote! {
382 #crate_ident::enums::FlagsValuesStorage<#nb_flags_values> = unsafe {
383 #crate_ident::enums::FlagsValuesStorage::<#nb_flags_values>::new([
384 #(#flags_values_iter)*
385 ])
386 }
387 };
388
389 if lazy_registration {
392 let registration_status_type = format_ident!("{}RegistrationStatus", name);
398 let registration_status = format_ident!(
400 "{}",
401 registration_status_type.to_string().to_shouty_snake_case()
402 );
403 let flags_values_array =
405 format_ident!("{}_VALUES", name.to_string().to_shouty_snake_case());
406
407 quote! {
408 struct #registration_status_type(<#plugin_ty as #crate_ident::clone::Downgrade>::Weak, #crate_ident::Type);
410 unsafe impl Send for #registration_status_type {}
411
412 static #registration_status: ::std::sync::Mutex<Option<#registration_status_type>> = ::std::sync::Mutex::new(None);
414
415 static #flags_values_array: #flags_values;
417
418 impl #name {
419 #[inline]
423 fn register_flags() -> #crate_ident::Type {
424 let mut registration_status = #registration_status.lock().unwrap();
425 match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
426 None => #crate_ident::Type::INVALID,
428 Some(#registration_status_type(type_plugin, type_)) if !type_.is_valid() => {
430 *type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_flags(type_plugin.upgrade().unwrap().as_ref(), #gtype_name, #flags_values_array.as_ref());
431 *type_
432 },
433 Some(#registration_status_type(_, type_)) => *type_
435 }
436 }
437
438 #[inline]
444 pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
445 let mut registration_status = #registration_status.lock().unwrap();
446 match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
447 None => {
449 *registration_status = Some(#registration_status_type(#crate_ident::clone::Downgrade::downgrade(type_plugin), #crate_ident::Type::INVALID));
450 true
451 },
452 Some(#registration_status_type(_, type_)) if type_.is_valid() => {
454 *type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_flags(type_plugin, #gtype_name, #flags_values_array.as_ref());
455 type_.is_valid()
456 },
457 Some(_) => {
459 true
460 }
461 }
462 }
463
464 #[inline]
468 pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
469 let mut registration_status = #registration_status.lock().unwrap();
470 match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
471 None => false,
473 Some(#registration_status_type(_, type_)) if type_.is_valid() => true,
475 Some(_) => {
477 *registration_status = None;
478 true
479 }
480 }
481 }
482 }
483 }
484 } else {
485 let gtype_status = format_ident!("{}_G_TYPE", name.to_string().to_shouty_snake_case());
489
490 quote! {
491 static #gtype_status: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(#crate_ident::gobject_ffi::G_TYPE_INVALID);
493
494 impl #name {
495 #[inline]
497 fn register_flags() -> #crate_ident::Type {
498 let gtype = #gtype_status.load(::std::sync::atomic::Ordering::Acquire);
499 unsafe { <#crate_ident::Type as #crate_ident::translate::FromGlib<#crate_ident::ffi::GType>>::from_glib(gtype) }
500 }
501
502 #[inline]
505 pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
506 static VALUES: #flags_values;
507 let gtype = #crate_ident::translate::IntoGlib::into_glib(<#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_flags(type_plugin, #gtype_name, VALUES.as_ref()));
508 #gtype_status.store(gtype, ::std::sync::atomic::Ordering::Release);
509 gtype != #crate_ident::gobject_ffi::G_TYPE_INVALID
510 }
511
512 #[inline]
514 pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
515 true
516 }
517 }
518 }
519 }
520}