Skip to content
This repository was archived by the owner on Mar 4, 2024. It is now read-only.

Add a GError derive macro #367

Merged
merged 4 commits into from
Mar 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 19 additions & 23 deletions glib-macros/src/genum_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<i32>, such as:
// if value == Animal::Goat as i32 {
// return Animal::Goat;
// }
fn gen_from_glib(enum_name: &Ident, enum_variants: &Punctuated<Variant, Comma>) -> 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 {
Expand Down Expand Up @@ -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! {
Expand All @@ -112,10 +95,23 @@ pub fn impl_genum(input: &syn::DeriveInput) -> TokenStream {
}
}

impl #crate_ident::translate::TryFromGlib<i32> for #name {
type Error = i32;

fn try_from_glib(value: i32) -> Result<Self, i32> {
let from_glib = || {
#from_glib
};

from_glib().ok_or(value)
}
}

impl #crate_ident::translate::FromGlib<i32> for #name {
unsafe fn from_glib(value: i32) -> Self {
#from_glib
unreachable!();
use #crate_ident::translate::TryFromGlib;

Self::try_from_glib(value).unwrap()
}
}

Expand Down
53 changes: 53 additions & 0 deletions glib-macros/src/gerror_domain_derive.rs
Original file line number Diff line number Diff line change
@@ -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<Self>
where
Self: Sized
{
#from_glib
}
}
}
}
27 changes: 27 additions & 0 deletions glib-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
///
Expand Down
65 changes: 63 additions & 2 deletions glib-macros/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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<Option<MetaList>> {
Expand Down Expand Up @@ -79,6 +83,38 @@ pub fn parse_type_name(input: &DeriveInput, attr_name: &str) -> Result<String> {
}
}

#[derive(Debug)]
pub enum ErrorDomainAttribute {
Name(String),
}

pub fn parse_error_attribute(meta: &NestedMeta) -> Result<ErrorDomainAttribute> {
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<String> {
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),
Expand Down Expand Up @@ -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<i32>, such as:
//
// if value == Animal::Goat as i32 {
// return Some(Animal::Goat);
// }
pub fn gen_enum_from_glib(
enum_name: &Ident,
enum_variants: &Punctuated<Variant, Comma>,
) -> 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
}
}
17 changes: 16 additions & 1 deletion glib-macros/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<TestError>());
assert!(matches!(err.kind::<TestError>(), Some(TestError::Bad)));
}

#[test]
fn derive_genum() {
Expand Down
7 changes: 6 additions & 1 deletion glib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down