1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use heck::ToKebabCase;
4use proc_macro::TokenStream;
5use proc_macro_error::abort;
6use quote::{format_ident, quote};
7use syn::{Data, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident, Type};
8
9use crate::utils::crate_ident_new;
10
11pub fn impl_variant(input: DeriveInput) -> TokenStream {
12 match input.data {
13 Data::Struct(data_struct: DataStruct) => {
14 derive_variant_for_struct(input.ident, input.generics, data_struct)
15 }
16 Data::Enum(data_enum: DataEnum) => {
17 let mode: EnumMode = get_enum_mode(&input.attrs);
18 let has_data: bool = data_enumIter<'_, Variant>
19 .variants
20 .iter()
21 .any(|v: &Variant| !matches!(v.fields, syn::Fields::Unit));
22 if has_data {
23 derive_variant_for_enum(input.ident, input.generics, data_enum, mode)
24 } else {
25 derive_variant_for_c_enum(input.ident, input.generics, data_enum, mode)
26 }
27 }
28 Data::Union(..) => {
29 panic!("#[derive(glib::Variant)] is not available for unions.");
30 }
31 }
32}
33
34fn derive_variant_for_struct(
35 ident: Ident,
36 generics: Generics,
37 data_struct: syn::DataStruct,
38) -> TokenStream {
39 let glib = crate_ident_new();
40 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
41 let (static_variant_type, to_variant, from_variant) = match data_struct.fields {
42 Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
43 let types = unnamed
44 .into_pairs()
45 .map(|pair| pair.into_value())
46 .map(|field| field.ty)
47 .collect::<Vec<_>>();
48
49 let idents = (0..types.len()).map(syn::Index::from).collect::<Vec<_>>();
50 let idents_len = idents.len();
51
52 let static_variant_type = quote! {
53 impl #impl_generics #glib::StaticVariantType for #ident #type_generics #where_clause {
54 #[inline]
55 fn static_variant_type() -> ::std::borrow::Cow<'static, #glib::VariantTy> {
56 static TYP: #glib::once_cell::sync::Lazy<#glib::VariantType> = #glib::once_cell::sync::Lazy::new(|| {
57
58 let mut builder = #glib::GStringBuilder::new("(");
59
60 #(
61 {
62 let typ = <#types as #glib::StaticVariantType>::static_variant_type();
63 builder.append(typ.as_str());
64 }
65 )*
66 builder.append_c(')');
67
68 #glib::VariantType::from_string(builder.into_string()).unwrap()
69 });
70
71 ::std::borrow::Cow::Borrowed(&*TYP)
72 }
73 }
74 };
75
76 let to_variant = quote! {
77 impl #impl_generics #glib::ToVariant for #ident #type_generics #where_clause {
78 fn to_variant(&self) -> #glib::Variant {
79 #glib::Variant::tuple_from_iter(::std::array::IntoIter::<#glib::Variant, #idents_len>::new([
80 #(
81 #glib::ToVariant::to_variant(&self.#idents)
82 ),*
83 ]))
84 }
85 }
86
87 impl #impl_generics ::std::convert::From<#ident #type_generics> for #glib::Variant #where_clause {
88 fn from(v: #ident #type_generics) -> #glib::Variant {
89 #glib::Variant::tuple_from_iter(::std::array::IntoIter::<#glib::Variant, #idents_len>::new([
90 #(
91 <#glib::Variant as ::std::convert::From<_>>::from(v.#idents)
92 ),*
93 ]))
94 }
95 }
96 };
97
98 let from_variant = quote! {
99 impl #impl_generics #glib::FromVariant for #ident #type_generics #where_clause {
100 fn from_variant(variant: &#glib::Variant) -> ::core::option::Option<Self> {
101 if !variant.is_container() {
102 return ::core::option::Option::None;
103 }
104 ::core::option::Option::Some(Self(
105 #(
106 match variant.try_child_get::<#types>(#idents) {
107 ::core::result::Result::Ok(::core::option::Option::Some(field)) => field,
108 _ => return ::core::option::Option::None,
109 }
110 ),*
111 ))
112 }
113 }
114 };
115
116 (static_variant_type, to_variant, from_variant)
117 }
118 Fields::Named(FieldsNamed { named, .. }) => {
119 let fields: Vec<(Ident, Type)> = named
120 .into_pairs()
121 .map(|pair| pair.into_value())
122 .map(|field| (field.ident.expect("Field ident is specified"), field.ty))
123 .collect();
124
125 let idents: Vec<_> = fields.iter().map(|(ident, _ty)| ident).collect();
126 let types: Vec<_> = fields.iter().map(|(_ident, ty)| ty).collect();
127 let counts = (0..types.len()).map(syn::Index::from).collect::<Vec<_>>();
128
129 let static_variant_type = quote! {
130 impl #impl_generics #glib::StaticVariantType for #ident #type_generics #where_clause {
131 #[inline]
132 fn static_variant_type() -> ::std::borrow::Cow<'static, #glib::VariantTy> {
133 static TYP: #glib::once_cell::sync::Lazy<#glib::VariantType> = #glib::once_cell::sync::Lazy::new(|| unsafe {
134 let ptr = #glib::ffi::g_string_sized_new(16);
135 #glib::ffi::g_string_append_c(ptr, b'(' as _);
136
137 #(
138 {
139 let typ = <#types as #glib::StaticVariantType>::static_variant_type();
140 #glib::ffi::g_string_append_len(
141 ptr,
142 typ.as_str().as_ptr() as *const _,
143 typ.as_str().len() as isize,
144 );
145 }
146 )*
147 #glib::ffi::g_string_append_c(ptr, b')' as _);
148
149 #glib::translate::from_glib_full(
150 #glib::ffi::g_string_free(ptr, #glib::ffi::GFALSE) as *mut #glib::ffi::GVariantType
151 )
152 });
153
154 ::std::borrow::Cow::Borrowed(&*TYP)
155 }
156 }
157 };
158
159 let to_variant = quote! {
160 impl #impl_generics #glib::ToVariant for #ident #type_generics #where_clause {
161 fn to_variant(&self) -> #glib::Variant {
162 #glib::Variant::tuple_from_iter(::std::iter::IntoIterator::into_iter([
163 #(
164 #glib::ToVariant::to_variant(&self.#idents)
165 ),*
166 ]))
167 }
168 }
169
170 impl #impl_generics ::std::convert::From<#ident #type_generics> for #glib::Variant #where_clause {
171 fn from(v: #ident #type_generics) -> #glib::Variant {
172 #glib::Variant::tuple_from_iter(::std::iter::IntoIterator::into_iter([
173 #(
174 <#glib::Variant as ::std::convert::From<_>>::from(v.#idents)
175 ),*
176 ]))
177 }
178 }
179 };
180
181 let from_variant = quote! {
182 impl #impl_generics #glib::FromVariant for #ident #type_generics #where_clause {
183 fn from_variant(variant: &#glib::Variant) -> ::core::option::Option<Self> {
184 if !variant.is_container() {
185 return ::core::option::Option::None;
186 }
187 ::core::option::Option::Some(Self {
188 #(
189 #idents: match variant.try_child_get::<#types>(#counts) {
190 ::core::result::Result::Ok(::core::option::Option::Some(field)) => field,
191 _ => return ::core::option::Option::None,
192 }
193 ),*
194 })
195 }
196 }
197 };
198
199 (static_variant_type, to_variant, from_variant)
200 }
201 Fields::Unit => {
202 let static_variant_type = quote! {
203 impl #impl_generics #glib::StaticVariantType for #ident #type_generics #where_clause {
204 #[inline]
205 fn static_variant_type() -> ::std::borrow::Cow<'static, #glib::VariantTy> {
206 ::std::borrow::Cow::Borrowed(#glib::VariantTy::UNIT)
207 }
208 }
209 };
210
211 let to_variant = quote! {
212 impl #impl_generics #glib::ToVariant for #ident #type_generics #where_clause {
213 #[inline]
214 fn to_variant(&self) -> #glib::Variant {
215 #glib::ToVariant::to_variant(&())
216 }
217 }
218
219 impl #impl_generics ::std::convert::From<#ident #type_generics> for #glib::Variant #where_clause {
220 #[inline]
221 fn from(v: #ident #type_generics) -> #glib::Variant {
222 #glib::ToVariant::to_variant(&())
223 }
224 }
225 };
226
227 let from_variant = quote! {
228 impl #impl_generics #glib::FromVariant for #ident #type_generics #where_clause {
229 fn from_variant(variant: &#glib::Variant) -> ::core::option::Option<Self> {
230 ::core::option::Option::Some(Self)
231 }
232 }
233 };
234
235 (static_variant_type, to_variant, from_variant)
236 }
237 };
238
239 let derived = quote! {
240 #static_variant_type
241
242 #to_variant
243
244 #from_variant
245 };
246
247 derived.into()
248}
249
250enum EnumMode {
251 String,
252 Repr(Ident),
253 Enum { repr: bool },
254 Flags { repr: bool },
255}
256
257impl EnumMode {
258 fn tag_type(&self) -> char {
259 match self {
260 EnumMode::String => 's',
261 EnumMode::Repr(repr) => match repr.to_string().as_str() {
262 "i8" | "i16" => 'n',
263 "i32" => 'i',
264 "i64" => 'x',
265 "u8" => 'y',
266 "u16" => 'q',
267 "u32" => 'u',
268 "u64" => 't',
269 _ => unimplemented!(),
270 },
271 EnumMode::Enum { repr } => {
272 if *repr {
273 'i'
274 } else {
275 's'
276 }
277 }
278 EnumMode::Flags { repr } => {
279 if *repr {
280 'u'
281 } else {
282 's'
283 }
284 }
285 }
286 }
287}
288
289fn derive_variant_for_enum(
290 ident: Ident,
291 generics: Generics,
292 data_enum: syn::DataEnum,
293 mode: EnumMode,
294) -> TokenStream {
295 let glib = crate_ident_new();
296 let static_variant_type = format!("({}v)", mode.tag_type());
297 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
298
299 let to = data_enum.variants.iter().enumerate().map(|(index, v)| {
300 let ident = &v.ident;
301 let tag = match &mode {
302 EnumMode::String => {
303 let nick = ToKebabCase::to_kebab_case(ident.to_string().as_str());
304 quote! { #nick }
305 },
306 EnumMode::Repr(repr) => quote! { #index as #repr },
307 _ => unimplemented!(),
308 };
309 if !matches!(v.fields, syn::Fields::Unit) {
310 match &mode {
311 EnumMode::Enum { .. } =>
312 abort!(v, "#[variant_enum(enum) only allowed with C-style enums using #[derive(glib::Enum)]"),
313 EnumMode::Flags { .. } =>
314 abort!(v, "#[variant_enum(flags) only allowed with bitflags using #[glib::flags]"),
315 _ => (),
316 }
317 }
318 match &v.fields {
319 syn::Fields::Named(FieldsNamed { named, .. }) => {
320 let field_names = named.iter().map(|f| f.ident.as_ref().unwrap());
321 let field_names2 = field_names.clone();
322 quote! {
323 Self::#ident { #(#field_names),* } => #glib::ToVariant::to_variant(&(
324 #tag,
325 #glib::Variant::tuple_from_iter(::std::iter::IntoIterator::into_iter([
326 #(#glib::ToVariant::to_variant(&#field_names2)),*
327 ]))
328 ))
329 }
330 },
331 syn::Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
332 let field_names = unnamed.iter().enumerate().map(|(i, _)| {
333 format_ident!("field{}", i)
334 });
335 let field_names2 = field_names.clone();
336 quote! {
337 Self::#ident(#(#field_names),*) => #glib::ToVariant::to_variant(&(
338 #tag,
339 #glib::Variant::tuple_from_iter(::std::iter::IntoIterator::into_iter([
340 #(#glib::ToVariant::to_variant(&#field_names2)),*
341 ]))
342 ))
343 }
344 },
345 syn::Fields::Unit => {
346 quote! {
347 Self::#ident => #glib::ToVariant::to_variant(&(
348 #tag,
349 #glib::ToVariant::to_variant(&())
350 ))
351 }
352 },
353 }
354 });
355 let into = data_enum.variants.iter().enumerate().map(|(index, v)| {
356 let field_ident = &v.ident;
357 let tag = match &mode {
358 EnumMode::String => {
359 let nick = ToKebabCase::to_kebab_case(field_ident.to_string().as_str());
360 quote! { #nick }
361 }
362 EnumMode::Repr(repr) => quote! { #index as #repr },
363 _ => unimplemented!(),
364 };
365 match &v.fields {
366 syn::Fields::Named(FieldsNamed { named, .. }) => {
367 let field_names = named.iter().map(|f| f.ident.as_ref().unwrap());
368 let field_names2 = field_names.clone();
369 quote! {
370 #ident::#field_ident { #(#field_names),* } => #glib::ToVariant::to_variant(&(
371 #tag,
372 #glib::Variant::tuple_from_iter(::std::iter::IntoIterator::into_iter([
373 #(<#glib::Variant as ::std::convert::From<_>>::from(#field_names2)),*
374 ]))
375 ))
376 }
377 }
378 syn::Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
379 let field_names = unnamed
380 .iter()
381 .enumerate()
382 .map(|(i, _)| format_ident!("field{}", i));
383 let field_names2 = field_names.clone();
384 quote! {
385 #ident::#field_ident(#(#field_names),*) => #glib::ToVariant::to_variant(&(
386 #tag,
387 #glib::Variant::tuple_from_iter(::std::iter::IntoIterator::into_iter([
388 #(<#glib::Variant as ::std::convert::From<_>>::from(#field_names2)),*
389 ]))
390 ))
391 }
392 }
393 syn::Fields::Unit => {
394 quote! {
395 #ident::#field_ident => #glib::ToVariant::to_variant(&(
396 #tag,
397 #glib::ToVariant::to_variant(&())
398 ))
399 }
400 }
401 }
402 });
403 let from = data_enum.variants.iter().enumerate().map(|(index, v)| {
404 let ident = &v.ident;
405 let tag = match &mode {
406 EnumMode::String => {
407 let nick = ToKebabCase::to_kebab_case(ident.to_string().as_str());
408 quote! { #nick }
409 }
410 EnumMode::Repr(_) => quote! { #index },
411 _ => unimplemented!(),
412 };
413 match &v.fields {
414 syn::Fields::Named(FieldsNamed { named, .. }) => {
415 let fields = named.iter().enumerate().map(|(index, f)| {
416 let name = f.ident.as_ref().unwrap();
417 let repr = &f.ty;
418 quote! {
419 #name: <#repr as #glib::FromVariant>::from_variant(
420 &#glib::Variant::try_child_value(&value, #index)?
421 )?
422 }
423 });
424 quote! { #tag => ::core::option::Option::Some(Self::#ident { #(#fields),* }), }
425 }
426 syn::Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
427 let indices = 0..unnamed.iter().count();
428 let repr = unnamed.iter().map(|f| &f.ty);
429 quote! {
430 #tag => ::core::option::Option::Some(Self::#ident(
431 #(
432 <#repr as #glib::FromVariant>::from_variant(
433 &#glib::Variant::try_child_value(&value, #indices)?
434 )?
435 ),*
436 )),
437 }
438 }
439 syn::Fields::Unit => {
440 quote! { #tag => ::core::option::Option::Some(Self::#ident), }
441 }
442 }
443 });
444
445 let (repr, tag_match) = match &mode {
446 EnumMode::String => (quote! { String }, quote! { tag.as_str() }),
447 EnumMode::Repr(repr) => (quote! { #repr }, quote! { tag as usize }),
448 _ => unimplemented!(),
449 };
450
451 let derived = quote! {
452 impl #impl_generics #glib::StaticVariantType for #ident #type_generics #where_clause {
453 #[inline]
454 fn static_variant_type() -> ::std::borrow::Cow<'static, #glib::VariantTy> {
455 ::std::borrow::Cow::Borrowed(
456 unsafe {
457 #glib::VariantTy::from_str_unchecked(#static_variant_type)
458 }
459 )
460 }
461 }
462
463 impl #impl_generics #glib::ToVariant for #ident #type_generics #where_clause {
464 fn to_variant(&self) -> #glib::Variant {
465 match self {
466 #(#to),*
467 }
468 }
469 }
470
471 impl #impl_generics ::std::convert::From<#ident #type_generics> for #glib::Variant #where_clause {
472 fn from(v: #ident #type_generics) -> #glib::Variant {
473 match v {
474 #(#into),*
475 }
476 }
477 }
478
479 impl #impl_generics #glib::FromVariant for #ident #type_generics #where_clause {
480 fn from_variant(variant: &#glib::Variant) -> ::core::option::Option<Self> {
481 let (tag, value) = <(#repr, #glib::Variant) as #glib::FromVariant>::from_variant(&variant)?;
482 if !#glib::VariantTy::is_tuple(#glib::Variant::type_(&value)) {
483 return ::core::option::Option::None;
484 }
485 match #tag_match {
486 #(#from)*
487 _ => ::core::option::Option::None
488 }
489 }
490 }
491 };
492 derived.into()
493}
494
495fn derive_variant_for_c_enum(
496 ident: Ident,
497 generics: Generics,
498 data_enum: syn::DataEnum,
499 mode: EnumMode,
500) -> TokenStream {
501 let glib = crate_ident_new();
502 let static_variant_type = mode.tag_type().to_string();
503 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
504
505 let (to_variant, from_variant) = match mode {
506 EnumMode::String => {
507 let idents = data_enum.variants.iter().map(|v| &v.ident);
508 let nicks = data_enum.variants.iter().map(|v| {
509 let nick = ToKebabCase::to_kebab_case(v.ident.to_string().as_str());
510 quote! { #nick }
511 });
512 let idents2 = idents.clone();
513 let nicks2 = nicks.clone();
514 (
515 quote! {
516 #glib::ToVariant::to_variant(match self {
517 #(Self::#idents => #nicks),*
518 })
519 },
520 quote! {
521 let tag = #glib::Variant::str(&variant)?;
522 match tag {
523 #(#nicks2 => ::core::option::Option::Some(Self::#idents2),)*
524 _ => ::core::option::Option::None
525 }
526 },
527 )
528 }
529 EnumMode::Repr(repr) => {
530 let idents = data_enum.variants.iter().map(|v| &v.ident);
531 (
532 quote! {
533 #glib::ToVariant::to_variant(&(*self as #repr))
534 },
535 quote! {
536 let value = <#repr as #glib::FromVariant>::from_variant(&variant)?;
537 #(if value == Self::#idents as #repr {
538 return ::core::option::Option::Some(Self::#idents);
539 })*
540 ::core::option::Option::None
541 },
542 )
543 }
544 EnumMode::Enum { repr: true } => (
545 quote! {
546 #glib::ToVariant::to_variant(&(*self as i32))
547 },
548 quote! {
549 let value = <i32 as #glib::FromVariant>::from_variant(&variant)?;
550 unsafe { #glib::translate::try_from_glib(value) }.ok()
551 },
552 ),
553 EnumMode::Enum { repr: false } => (
554 quote! {
555 let enum_class = #glib::EnumClass::new::<Self>();
556 let value = <Self as #glib::translate::IntoGlib>::into_glib(*self);
557 let value = #glib::EnumClass::value(&enum_class, value);
558 let value = ::core::option::Option::unwrap(value);
559 let nick = #glib::EnumValue::nick(&value);
560 #glib::ToVariant::to_variant(nick)
561 },
562 quote! {
563 let enum_class = #glib::EnumClass::new::<Self>();
564 let tag = #glib::Variant::str(&variant)?;
565 let value = #glib::EnumClass::value_by_nick(&enum_class, tag)?;
566 let value = #glib::EnumValue::value(&value);
567 unsafe { #glib::translate::try_from_glib(value) }.ok()
568 },
569 ),
570 EnumMode::Flags { repr: true } => (
571 quote! {
572 #glib::ToVariant::to_variant(&self.bits())
573 },
574 quote! {
575 let value = <u32 as #glib::FromVariant>::from_variant(&variant)?;
576 Self::from_bits(value)
577 },
578 ),
579 EnumMode::Flags { repr: false } => (
580 quote! {
581 let flags_class = #glib::FlagsClass::new::<Self>();
582 let value = <Self as #glib::translate::IntoGlib>::into_glib(*self);
583 let s = #glib::FlagsClass::to_nick_string(&flags_class, value);
584 #glib::ToVariant::to_variant(&s)
585 },
586 quote! {
587 let flags_class = #glib::FlagsClass::new::<Self>();
588 let s = #glib::Variant::str(&variant)?;
589 let value = #glib::FlagsClass::from_nick_string(&flags_class, s).ok()?;
590 ::core::option::Option::Some(unsafe { #glib::translate::from_glib(value) })
591 },
592 ),
593 };
594
595 let derived = quote! {
596 impl #impl_generics #glib::StaticVariantType for #ident #type_generics #where_clause {
597 #[inline]
598 fn static_variant_type() -> ::std::borrow::Cow<'static, #glib::VariantTy> {
599 ::std::borrow::Cow::Borrowed(
600 unsafe {
601 #glib::VariantTy::from_str_unchecked(#static_variant_type)
602 }
603 )
604 }
605 }
606
607 impl #impl_generics #glib::ToVariant for #ident #type_generics #where_clause {
608 fn to_variant(&self) -> #glib::Variant {
609 #to_variant
610 }
611 }
612
613 impl #impl_generics ::std::convert::From<#ident #type_generics> for #glib::Variant #where_clause {
614 #[inline]
615 fn from(v: #ident #type_generics) -> #glib::Variant {
616 <#ident #type_generics as #glib::ToVariant>::to_variant(&v)
617 }
618 }
619
620 impl #impl_generics #glib::FromVariant for #ident #type_generics #where_clause {
621 fn from_variant(variant: &#glib::Variant) -> ::core::option::Option<Self> {
622 #from_variant
623 }
624 }
625 };
626 derived.into()
627}
628
629fn get_enum_mode(attrs: &[syn::Attribute]) -> EnumMode {
630 let attr = attrs.iter().find(|a| a.path().is_ident("variant_enum"));
631
632 let attr = match attr {
633 Some(attr) => attr,
634 None => return EnumMode::String,
635 };
636
637 let mut repr_attr = None;
638 let mut mode = EnumMode::String;
639 attr.parse_nested_meta(|meta| {
640 match meta.path.get_ident().map(|id| id.to_string()).as_deref() {
641 Some("repr") => {
642 repr_attr = Some(meta.path);
643 }
644 Some("enum") => {
645 mode = EnumMode::Enum { repr: false };
646 }
647 Some("flags") => {
648 mode = EnumMode::Flags { repr: false };
649 }
650 _ => abort!(meta.path, "unknown type in #[variant_enum] attribute"),
651 }
652 Ok(())
653 })
654 .unwrap();
655 match mode {
656 EnumMode::String if repr_attr.is_some() => {
657 let repr_attr = repr_attr.unwrap();
658 let repr = get_repr(attrs).unwrap_or_else(|| {
659 abort!(
660 repr_attr,
661 "Must have #[repr] attribute with one of i8, i16, i32, i64, u8, u16, u32, u64"
662 )
663 });
664 EnumMode::Repr(repr)
665 }
666 EnumMode::Enum { .. } => EnumMode::Enum {
667 repr: repr_attr.is_some(),
668 },
669 EnumMode::Flags { .. } => EnumMode::Flags {
670 repr: repr_attr.is_some(),
671 },
672 e => e,
673 }
674}
675
676fn get_repr(attrs: &[syn::Attribute]) -> Option<Ident> {
677 let attr: &Attribute = attrs.iter().find(|a: &&Attribute| a.path().is_ident("repr"))?;
678 let mut repr_ty: Option = None;
679 attrResult<(), Error>.parse_nested_meta(|meta: ParseNestedMeta<'_>| {
680 repr_ty = Some(meta.path.get_ident().unwrap().clone());
681 Ok(())
682 })
683 .unwrap();
684 match repr_ty.as_ref()?.to_string().as_str() {
685 "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => Some(repr_ty?),
686 _ => None,
687 }
688}
689