diff --git a/glib-macros/src/genum_derive.rs b/glib-macros/src/genum_derive.rs index 526743d3acdf..a66e95124ef6 100644 --- a/glib-macros/src/genum_derive.rs +++ b/glib-macros/src/genum_derive.rs @@ -6,26 +6,9 @@ use proc_macro_error::abort_call_site; use quote::{format_ident, quote, quote_spanned}; use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Data, Ident, Variant}; -use crate::utils::{crate_ident_new, parse_item_attributes, parse_type_name, ItemAttribute}; - -// Generate i32 to enum mapping, used to implement glib::translate::FromGlib, such as: -// if value == Animal::Goat as i32 { -// return Animal::Goat; -// } -fn gen_from_glib(enum_name: &Ident, enum_variants: &Punctuated) -> TokenStream { - // FIXME: can we express this with a match()? - let recurse = enum_variants.iter().map(|v| { - let name = &v.ident; - quote_spanned! {v.span()=> - if value == #enum_name::#name as i32 { - return #enum_name::#name; - } - } - }); - quote! { - #(#recurse)* - } -} +use crate::utils::{ + crate_ident_new, gen_enum_from_glib, parse_item_attributes, parse_type_name, ItemAttribute, +}; // Generate glib::gobject_ffi::GEnumValue structs mapping the enum such as: // glib::gobject_ffi::GEnumValue { @@ -100,7 +83,7 @@ pub fn impl_genum(input: &syn::DeriveInput) -> TokenStream { ), }; let get_type = format_ident!("{}_get_type", name.to_string().to_snake_case()); - let from_glib = gen_from_glib(name, enum_variants); + let from_glib = gen_enum_from_glib(name, enum_variants); let (genum_values, nb_genum_values) = gen_genum_values(name, enum_variants); quote! { @@ -112,10 +95,23 @@ pub fn impl_genum(input: &syn::DeriveInput) -> TokenStream { } } + impl #crate_ident::translate::TryFromGlib for #name { + type Error = i32; + + fn try_from_glib(value: i32) -> Result { + let from_glib = || { + #from_glib + }; + + from_glib().ok_or(value) + } + } + impl #crate_ident::translate::FromGlib for #name { unsafe fn from_glib(value: i32) -> Self { - #from_glib - unreachable!(); + use #crate_ident::translate::TryFromGlib; + + Self::try_from_glib(value).unwrap() } } diff --git a/glib-macros/src/gerror_domain_derive.rs b/glib-macros/src/gerror_domain_derive.rs new file mode 100644 index 000000000000..b16f5be546b3 --- /dev/null +++ b/glib-macros/src/gerror_domain_derive.rs @@ -0,0 +1,53 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use proc_macro2::TokenStream; +use proc_macro_error::abort_call_site; +use quote::quote; +use syn::Data; + +use crate::utils::{crate_ident_new, gen_enum_from_glib, parse_name}; + +pub fn impl_gerror_domain(input: &syn::DeriveInput) -> TokenStream { + let name = &input.ident; + + let crate_ident = crate_ident_new(); + + let enum_variants = match input.data { + Data::Enum(ref e) => &e.variants, + _ => abort_call_site!("GErrorDomain only supports enums"), + }; + + let domain_name = match parse_name(&input, "gerror_domain") { + Ok(v) => v, + Err(e) => abort_call_site!( + "{}: derive(GErrorDomain) requires #[gerror_domain(name = \"DomainName\")]", + e + ), + }; + let from_glib = gen_enum_from_glib(name, enum_variants); + + quote! { + impl #crate_ident::error::ErrorDomain for #name { + fn domain() -> #crate_ident::Quark { + use #crate_ident::translate::from_glib; + + static QUARK: #crate_ident::once_cell::sync::Lazy<#crate_ident::Quark> = + #crate_ident::once_cell::sync::Lazy::new(|| unsafe { + from_glib(#crate_ident::ffi::g_quark_from_static_string(concat!(#domain_name, "\0") as *const str as *const _)) + }); + *QUARK + } + + fn code(self) -> i32 { + self as i32 + } + + fn from(value: i32) -> Option + where + Self: Sized + { + #from_glib + } + } + } +} diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 0122ba3c7ec3..229bc0fd6ddf 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -4,6 +4,7 @@ mod clone; mod downgrade_derive; mod gboxed_derive; mod genum_derive; +mod gerror_domain_derive; mod gflags_attribute; mod object_interface_attribute; mod object_subclass_attribute; @@ -249,6 +250,32 @@ pub fn genum_derive(input: TokenStream) -> TokenStream { gen.into() } +/// Derive macro for defining a GLib error domain and its associated +/// [`ErrorDomain`] trait. +/// +/// # Example +/// +/// ``` +/// use glib::prelude::*; +/// use glib::subclass::prelude::*; +/// +/// #[derive(Debug, Copy, Clone, glib::GErrorDomain)] +/// #[gerror_domain(name = "ExFoo")] +/// enum Foo { +/// Blah, +/// Baaz, +/// } +/// ``` +/// +/// [`ErrorDomain`]: error/trait.ErrorDomain.html +#[proc_macro_derive(GErrorDomain, attributes(gerror_domain))] +#[proc_macro_error] +pub fn gerror_doman_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let gen = gerror_domain_derive::impl_gerror_domain(&input); + gen.into() +} + /// Derive macro for defining a [`BoxedType`]`::get_type` function and /// the [`glib::Value`] traits. /// diff --git a/glib-macros/src/utils.rs b/glib-macros/src/utils.rs index 151858459d14..cd5c01276738 100644 --- a/glib-macros/src/utils.rs +++ b/glib-macros/src/utils.rs @@ -1,9 +1,13 @@ // Take a look at the license at the top of the repository in the LICENSE file. use anyhow::{bail, Result}; -use proc_macro2::{Ident, Span}; +use proc_macro2::{Ident, Span, TokenStream}; use proc_macro_crate::crate_name; -use syn::{Attribute, DeriveInput, Lit, Meta, MetaList, NestedMeta}; +use quote::{quote, quote_spanned}; +use syn::{ + punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DeriveInput, Lit, Meta, + MetaList, NestedMeta, Variant, +}; // find the #[@attr_name] attribute in @attrs pub fn find_attribute_meta(attrs: &[Attribute], attr_name: &str) -> Result> { @@ -79,6 +83,38 @@ pub fn parse_type_name(input: &DeriveInput, attr_name: &str) -> Result { } } +#[derive(Debug)] +pub enum ErrorDomainAttribute { + Name(String), +} + +pub fn parse_error_attribute(meta: &NestedMeta) -> Result { + let (ident, v) = parse_attribute(meta)?; + + match ident.as_ref() { + "name" => Ok(ErrorDomainAttribute::Name(v)), + s => bail!("Unknown enum meta {}", s), + } +} + +// Parse attribute such as: +// #[gerror_domain(name = "MyError")] +pub fn parse_name(input: &DeriveInput, attr_name: &str) -> Result { + let meta = match find_attribute_meta(&input.attrs, attr_name)? { + Some(meta) => meta, + _ => bail!("Missing '{}' attribute", attr_name), + }; + + let meta = match find_nested_meta(&meta, "name") { + Some(meta) => meta, + _ => bail!("Missing meta 'name'"), + }; + + match parse_error_attribute(&meta)? { + ErrorDomainAttribute::Name(n) => Ok(n), + } +} + #[derive(Debug)] pub enum ItemAttribute { Name(String), @@ -124,3 +160,28 @@ pub fn crate_ident_new() -> Ident { Ident::new(&crate_name, Span::call_site()) } + +// Generate i32 to enum mapping, used to implement +// glib::translate::TryFromGlib, such as: +// +// if value == Animal::Goat as i32 { +// return Some(Animal::Goat); +// } +pub fn gen_enum_from_glib( + enum_name: &Ident, + enum_variants: &Punctuated, +) -> TokenStream { + // FIXME: can we express this with a match()? + let recurse = enum_variants.iter().map(|v| { + let name = &v.ident; + quote_spanned! { v.span() => + if value == #enum_name::#name as i32 { + return Some(#enum_name::#name); + } + } + }); + quote! { + #(#recurse)* + None + } +} diff --git a/glib-macros/tests/test.rs b/glib-macros/tests/test.rs index 342c9b4f4f4c..4bf8375b5bfc 100644 --- a/glib-macros/tests/test.rs +++ b/glib-macros/tests/test.rs @@ -5,7 +5,22 @@ use glib::prelude::*; use glib::subclass::prelude::*; use glib::translate::{FromGlib, ToGlib}; -use glib::{gflags, GBoxed, GEnum}; +use glib::{gflags, GBoxed, GEnum, GErrorDomain}; + +#[test] +fn derive_gerror_domain() { + #[derive(Debug, Eq, PartialEq, Clone, Copy, GErrorDomain)] + #[gerror_domain(name = "TestError")] + enum TestError { + Invalid, + Bad, + Wrong, + } + + let err = glib::Error::new(TestError::Bad, "oh no!"); + assert!(err.is::()); + assert!(matches!(err.kind::(), Some(TestError::Bad))); +} #[test] fn derive_genum() { diff --git a/glib/src/lib.rs b/glib/src/lib.rs index 0d5d9278fc25..80aa91295986 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -83,7 +83,12 @@ pub use gobject_ffi; #[doc(hidden)] pub use bitflags; -pub use glib_macros::{clone, gflags, object_interface, object_subclass, Downgrade, GBoxed, GEnum}; +#[doc(hidden)] +pub use once_cell; + +pub use glib_macros::{ + clone, gflags, object_interface, object_subclass, Downgrade, GBoxed, GEnum, GErrorDomain, +}; pub use self::byte_array::ByteArray; pub use self::bytes::Bytes;