1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use heck::{ToKebabCase, ToUpperCamelCase};
4use proc_macro2::TokenStream;
5use proc_macro_error::abort_call_site;
6use quote::{quote, quote_spanned};
7use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Data, Ident, Variant};
8
9use crate::utils::{crate_ident_new, gen_enum_from_glib, parse_nested_meta_items, NestedMetaItem};
10
11// Generate glib::gobject_ffi::GEnumValue structs mapping the enum such as:
12// glib::gobject_ffi::GEnumValue {
13// value: Animal::Goat as i32,
14// value_name: "Goat\0" as *const _ as *const _,
15// value_nick: "goat\0" as *const _ as *const _,
16// },
17fn gen_enum_values(
18 enum_name: &Ident,
19 enum_variants: &Punctuated<Variant, Comma>,
20) -> (TokenStream, usize) {
21 let crate_ident = crate_ident_new();
22
23 // start at one as GEnumValue array is null-terminated
24 let mut n = 1;
25 let recurse = enum_variants.iter().map(|v| {
26 let name = &v.ident;
27 let mut value_name = name.to_string().to_upper_camel_case();
28 let mut value_nick = name.to_string().to_kebab_case();
29
30 let mut name_attr = NestedMetaItem::<syn::LitStr>::new("name").value_required();
31 let mut nick = NestedMetaItem::<syn::LitStr>::new("nick").value_required();
32
33 let found =
34 parse_nested_meta_items(&v.attrs, "enum_value", &mut [&mut name_attr, &mut nick]);
35 if let Err(e) = found {
36 return e.to_compile_error();
37 }
38
39 value_name = name_attr.value.map(|s| s.value()).unwrap_or(value_name);
40 value_nick = nick.value.map(|s| s.value()).unwrap_or(value_nick);
41
42 let value_name = format!("{value_name}\0");
43 let value_nick = format!("{value_nick}\0");
44
45 n += 1;
46 quote_spanned! {v.span()=>
47 #crate_ident::gobject_ffi::GEnumValue {
48 value: #enum_name::#name as i32,
49 value_name: #value_name as *const _ as *const _,
50 value_nick: #value_nick as *const _ as *const _,
51 },
52 }
53 });
54 (
55 quote! {
56 #(#recurse)*
57 },
58 n,
59 )
60}
61
62pub fn impl_enum(input: &syn::DeriveInput) -> TokenStream {
63 let name = &input.ident;
64
65 let enum_variants = match input.data {
66 Data::Enum(ref e) => &e.variants,
67 _ => abort_call_site!("#[derive(glib::Enum)] only supports enums"),
68 };
69
70 let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name")
71 .required()
72 .value_required();
73 let found = parse_nested_meta_items(&input.attrs, "enum_type", &mut [&mut gtype_name]);
74
75 match found {
76 Ok(None) => {
77 abort_call_site!("#[derive(glib::Enum)] requires #[enum_type(name = \"EnumTypeName\")]")
78 }
79 Err(e) => return e.to_compile_error(),
80 Ok(attr) => attr,
81 };
82 let gtype_name = gtype_name.value.unwrap();
83 let from_glib = gen_enum_from_glib(name, enum_variants);
84 let (enum_values, nb_enum_values) = gen_enum_values(name, enum_variants);
85
86 let crate_ident = crate_ident_new();
87
88 quote! {
89 impl #crate_ident::translate::IntoGlib for #name {
90 type GlibType = i32;
91
92 #[inline]
93 fn into_glib(self) -> i32 {
94 self as i32
95 }
96 }
97
98 impl #crate_ident::translate::TryFromGlib<i32> for #name {
99 type Error = i32;
100
101 #[inline]
102 unsafe fn try_from_glib(value: i32) -> ::core::result::Result<Self, i32> {
103 let from_glib = || {
104 #from_glib
105 };
106
107 from_glib().ok_or(value)
108 }
109 }
110
111 impl #crate_ident::translate::FromGlib<i32> for #name {
112 #[inline]
113 unsafe fn from_glib(value: i32) -> Self {
114 use #crate_ident::translate::TryFromGlib;
115
116 Self::try_from_glib(value).unwrap()
117 }
118 }
119
120 impl #crate_ident::value::ValueType for #name {
121 type Type = Self;
122 }
123
124 unsafe impl<'a> #crate_ident::value::FromValue<'a> for #name {
125 type Checker = #crate_ident::value::GenericValueTypeChecker<Self>;
126
127 #[inline]
128 unsafe fn from_value(value: &'a #crate_ident::value::Value) -> Self {
129 #crate_ident::translate::from_glib(#crate_ident::gobject_ffi::g_value_get_enum(
130 #crate_ident::translate::ToGlibPtr::to_glib_none(value).0
131 ))
132 }
133 }
134
135 impl #crate_ident::value::ToValue for #name {
136 #[inline]
137 fn to_value(&self) -> #crate_ident::value::Value {
138 let mut value = #crate_ident::value::Value::for_value_type::<Self>();
139 unsafe {
140 #crate_ident::gobject_ffi::g_value_set_enum(
141 #crate_ident::translate::ToGlibPtrMut::to_glib_none_mut(&mut value).0,
142 #crate_ident::translate::IntoGlib::into_glib(*self)
143 )
144 }
145 value
146 }
147
148 #[inline]
149 fn value_type(&self) -> #crate_ident::Type {
150 <Self as #crate_ident::StaticType>::static_type()
151 }
152 }
153
154 impl ::std::convert::From<#name> for #crate_ident::Value {
155 #[inline]
156 fn from(v: #name) -> Self {
157 #crate_ident::value::ToValue::to_value(&v)
158 }
159 }
160
161 impl #crate_ident::StaticType for #name {
162 #[inline]
163 fn static_type() -> #crate_ident::Type {
164 static ONCE: ::std::sync::Once = ::std::sync::Once::new();
165 static mut TYPE: #crate_ident::Type = #crate_ident::Type::INVALID;
166
167 ONCE.call_once(|| {
168 static mut VALUES: [#crate_ident::gobject_ffi::GEnumValue; #nb_enum_values] = [
169 #enum_values
170 #crate_ident::gobject_ffi::GEnumValue {
171 value: 0,
172 value_name: ::std::ptr::null(),
173 value_nick: ::std::ptr::null(),
174 },
175 ];
176
177 let name = ::std::ffi::CString::new(#gtype_name).expect("CString::new failed");
178 unsafe {
179 let type_ = #crate_ident::gobject_ffi::g_enum_register_static(name.as_ptr(), VALUES.as_ptr());
180 let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_);
181 assert!(type_.is_valid());
182 TYPE = type_;
183 }
184 });
185
186 unsafe {
187 TYPE
188 }
189 }
190 }
191
192 impl #crate_ident::HasParamSpec for #name {
193 type ParamSpec = #crate_ident::ParamSpecEnum;
194 type SetValue = Self;
195 type BuilderFn = fn(&::core::primitive::str, Self) -> #crate_ident::ParamSpecEnumBuilder<Self>;
196
197 fn param_spec_builder() -> Self::BuilderFn {
198 |name, default_value| Self::ParamSpec::builder_with_default(name, default_value)
199 }
200 }
201 }
202}
203