1 // Take a look at the license at the top of the repository in the LICENSE file.
2
3 use heck::{CamelCase, KebabCase};
4 use proc_macro2::TokenStream;
5 use proc_macro_error::abort_call_site;
6 use quote::{quote, quote_spanned};
7 use syn::{
8 punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DeriveInput, Ident,
9 LitStr, Variant, Visibility,
10 };
11
12 use crate::utils::{
13 crate_ident_new, find_attribute_meta, find_nested_meta, parse_item_attributes, ItemAttribute,
14 };
15
16 // Flag is not registered if it has the #[gflags(skip)] meta
attribute_has_skip(attrs: &[Attribute]) -> bool17 fn attribute_has_skip(attrs: &[Attribute]) -> bool {
18 let meta = find_attribute_meta(attrs, "gflags").unwrap();
19
20 match meta {
21 None => false,
22 Some(meta) => find_nested_meta(&meta, "skip").is_some(),
23 }
24 }
25
26 // Generate glib::gobject_ffi::GFlagsValue structs mapping the enum such as:
27 // glib::gobject_ffi::GFlagsValue {
28 // value: MyFlags::A.bits(),
29 // value_name: "The Name\0" as *const _ as *const _,
30 // value_nick: "nick\0" as *const _ as *const _,
31 // },
gen_gflags_values( enum_name: &Ident, enum_variants: &Punctuated<Variant, Comma>, ) -> (TokenStream, usize)32 fn gen_gflags_values(
33 enum_name: &Ident,
34 enum_variants: &Punctuated<Variant, Comma>,
35 ) -> (TokenStream, usize) {
36 let crate_ident = crate_ident_new();
37
38 // start at one as GFlagsValue array is null-terminated
39 let mut n = 1;
40 let recurse = enum_variants.iter().filter(|v| { !attribute_has_skip(&v.attrs) } ).map(|v| {
41 let name = &v.ident;
42 let mut value_name = name.to_string().to_camel_case();
43 let mut value_nick = name.to_string().to_kebab_case();
44
45 let attrs = parse_item_attributes("gflags", &v.attrs);
46 let attrs = match attrs {
47 Ok(attrs) => attrs,
48 Err(e) => abort_call_site!(
49 "{}: gflags enum supports only the following optional attributes: #[gflags(name = \"The Name\", nick = \"the-nick\")] or #[gflags(skip)]",
50 e
51 ),
52 };
53
54 attrs.into_iter().for_each(|attr|
55 match attr {
56 ItemAttribute::Name(n) => value_name = n,
57 ItemAttribute::Nick(n) => value_nick = n,
58 }
59 );
60
61 let value_name = format!("{}\0", value_name);
62 let value_nick = format!("{}\0", value_nick);
63
64 n += 1;
65 quote_spanned! {v.span()=>
66 #crate_ident::gobject_ffi::GFlagsValue {
67 value: #enum_name::#name.bits(),
68 value_name: #value_name as *const _ as *const _,
69 value_nick: #value_nick as *const _ as *const _,
70 },
71 }
72 });
73 (
74 quote! {
75 #(#recurse)*
76 },
77 n,
78 )
79 }
80
gen_bitflags( enum_name: &Ident, visibility: &Visibility, enum_variants: &Punctuated<Variant, Comma>, crate_ident: &TokenStream, ) -> TokenStream81 fn gen_bitflags(
82 enum_name: &Ident,
83 visibility: &Visibility,
84 enum_variants: &Punctuated<Variant, Comma>,
85 crate_ident: &TokenStream,
86 ) -> TokenStream {
87 let recurse = enum_variants.iter().map(|v| {
88 let name = &v.ident;
89 let disc = v.discriminant.as_ref().expect("missing discriminant");
90 let value = &disc.1;
91
92 quote_spanned! {v.span()=>
93 const #name = #value;
94 }
95 });
96
97 quote! {
98 #crate_ident::bitflags::bitflags! {
99 #visibility struct #enum_name: u32 {
100 #(#recurse)*
101 }
102 }
103 }
104 }
105
impl_gflags(input: &DeriveInput, gtype_name: &LitStr) -> TokenStream106 pub fn impl_gflags(input: &DeriveInput, gtype_name: &LitStr) -> TokenStream {
107 let visibility = &input.vis;
108 let name = &input.ident;
109 let crate_ident = crate_ident_new();
110
111 let enum_variants = match input.data {
112 Data::Enum(ref e) => &e.variants,
113 _ => abort_call_site!("gflags only supports enums"),
114 };
115
116 let bitflags = gen_bitflags(name, visibility, enum_variants, &crate_ident);
117 let (gflags_values, nb_gflags_values) = gen_gflags_values(name, enum_variants);
118
119 quote! {
120 #bitflags
121
122 impl #crate_ident::translate::IntoGlib for #name {
123 type GlibType = u32;
124
125 fn into_glib(self) -> u32 {
126 self.bits()
127 }
128 }
129
130 impl #crate_ident::translate::FromGlib<u32> for #name {
131 unsafe fn from_glib(value: u32) -> Self {
132 Self::from_bits_truncate(value)
133 }
134 }
135
136 impl #crate_ident::value::ValueType for #name {
137 type Type = Self;
138 }
139
140 unsafe impl<'a> #crate_ident::value::FromValue<'a> for #name {
141 type Checker = #crate_ident::value::GenericValueTypeChecker<Self>;
142
143 unsafe fn from_value(value: &'a #crate_ident::value::Value) -> Self {
144 #crate_ident::translate::from_glib(#crate_ident::gobject_ffi::g_value_get_flags(
145 #crate_ident::translate::ToGlibPtr::to_glib_none(value).0
146 ))
147 }
148 }
149
150 impl #crate_ident::value::ToValue for #name {
151 fn to_value(&self) -> #crate_ident::value::Value {
152 let mut value = #crate_ident::value::Value::for_value_type::<Self>();
153 unsafe {
154 #crate_ident::gobject_ffi::g_value_set_flags(
155 #crate_ident::translate::ToGlibPtrMut::to_glib_none_mut(&mut value).0,
156 #crate_ident::translate::IntoGlib::into_glib(*self)
157 )
158 }
159 value
160 }
161
162 fn value_type(&self) -> #crate_ident::Type {
163 <Self as #crate_ident::StaticType>::static_type()
164 }
165 }
166
167 impl #crate_ident::StaticType for #name {
168 fn static_type() -> #crate_ident::Type {
169 static ONCE: std::sync::Once = std::sync::Once::new();
170 static mut TYPE: #crate_ident::Type = #crate_ident::Type::INVALID;
171
172 ONCE.call_once(|| {
173 static mut VALUES: [#crate_ident::gobject_ffi::GFlagsValue; #nb_gflags_values] = [
174 #gflags_values
175 #crate_ident::gobject_ffi::GFlagsValue {
176 value: 0,
177 value_name: std::ptr::null(),
178 value_nick: std::ptr::null(),
179 },
180 ];
181
182 let name = std::ffi::CString::new(#gtype_name).expect("CString::new failed");
183 unsafe {
184 let type_ = #crate_ident::gobject_ffi::g_flags_register_static(name.as_ptr(), VALUES.as_ptr());
185 TYPE = #crate_ident::translate::from_glib(type_);
186 }
187 });
188
189 unsafe {
190 assert!(TYPE.is_valid());
191 TYPE
192 }
193 }
194 }
195 }
196 }
197