Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try from delegate #1048

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
23 changes: 23 additions & 0 deletions glib-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,29 @@ pub fn derive_props(input: TokenStream) -> TokenStream {
/// assert_eq!(convertedv.get::<Option<MyString>>(), Ok(myv));
/// let convertedv = None::<MyString>.to_value();
/// assert_eq!(convertedv.get::<Option<MyString>>(), Ok(None::<MyString>));
///
///
/// // if the conversion can fail, use `try_from`
/// #[derive(ValueDelegate, Debug, PartialEq)]
/// #[value_delegate(try_from = u32)]
/// struct MyUnsigned(u16);
///
/// impl TryFrom<u32> for MyUnsigned {
/// type Error = std::num::TryFromIntError;
/// fn try_from(value: u32) -> Result<Self, Self::Error> {
/// Ok(MyUnsigned(u16::try_from(value)?))
/// }
/// }
/// impl<'a> From<&'a MyUnsigned> for u32 {
/// fn from(v: &'a MyUnsigned) -> Self {
/// v.0.into()
/// }
/// }
///
///
/// let valid_u32: u32 = 42;
/// let convertedv = valid_u32.to_value();
/// assert_eq!(valid_u32, convertedv.get::<MyUnsigned>().unwrap().0 as u32)
/// ```
#[proc_macro_derive(ValueDelegate, attributes(value_delegate))]
pub fn derive_value_delegate(input: TokenStream) -> TokenStream {
Expand Down
77 changes: 47 additions & 30 deletions glib-macros/src/value_delegate_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::utils::crate_ident_new;
#[derive(Default, Debug, Clone)]
enum DeriveMode {
From,
TryFrom,
#[default]
Private,
}
Expand All @@ -21,22 +22,27 @@ pub struct ValueDelegateInput {

enum Arg {
FromPath(syn::Path),
TryFromPath(syn::Path),
Nullable,
}

impl Parse for Arg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let argname: syn::Ident = input.parse()?;
if argname == "nullable" {
Ok(Arg::Nullable)
} else if argname == "from" {
let _eq: Token![=] = input.parse()?;
Ok(Arg::FromPath(input.parse()?))
} else {
Err(syn::Error::new(
match argname.to_string().as_ref() {
"nullable" => Ok(Arg::Nullable),
"from" => {
let _eq: Token![=] = input.parse()?;
Ok(Arg::FromPath(input.parse()?))
}
"try_from" => {
let _eq: Token![=] = input.parse()?;
Ok(Arg::TryFromPath(input.parse()?))
}
_ => Err(syn::Error::new(
input.span(),
"expected `nullable` or `from`",
))
)),
}
}
}
Expand All @@ -45,6 +51,7 @@ impl Parse for Arg {
struct Args {
nullable: bool,
from_path: Option<syn::Path>,
try_from_path: Option<syn::Path>,
}

impl Parse for Args {
Expand All @@ -54,6 +61,7 @@ impl Parse for Args {
for a in args {
match a {
Arg::FromPath(p) => this.from_path = Some(p),
Arg::TryFromPath(p) => this.try_from_path = Some(p),
Arg::Nullable => this.nullable = true,
}
}
Expand All @@ -64,49 +72,52 @@ impl Parse for Args {
impl Parse for ValueDelegateInput {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let derive_input: syn::DeriveInput = input.parse()?;
let args: Option<Args> = if let Some(attr) = derive_input
let args = if let Some(attr) = derive_input
.attrs
.iter()
.find(|x| x.path.is_ident("value_delegate"))
{
let args: Args = attr.parse_args()?;
Some(args)
args
} else {
None
Args::default()
};

let (delegated_ty, mode) =
if let Some(path) = args.as_ref().and_then(|a| a.from_path.as_ref()) {
(Some(path.clone()), DeriveMode::From)
} else {
let path = match derive_input.data {
syn::Data::Struct(s) => match s.fields {
syn::Fields::Unnamed(fields) if fields.unnamed.iter().count() == 1 => {
fields.unnamed.into_iter().next().and_then(|x| match x.ty {
syn::Type::Path(p) => Some(p.path),
_ => None,
})
}
_ => None,
},
let (delegated_ty, mode) = if let Some(ref path) = args.from_path {
(Some(path.clone()), DeriveMode::From)
} else if let Some(ref path) = args.try_from_path {
(Some(path.clone()), DeriveMode::TryFrom)
} else {
let path = match derive_input.data {
syn::Data::Struct(s) => match s.fields {
syn::Fields::Unnamed(fields) if fields.unnamed.iter().count() == 1 => {
fields.unnamed.into_iter().next().and_then(|x| match x.ty {
syn::Type::Path(p) => Some(p.path),
_ => None,
})
}
_ => None,
};
(path, DeriveMode::Private)
},
_ => None,
};
(path, DeriveMode::Private)
};
let delegated_ty = delegated_ty.ok_or_else(|| {
syn::Error::new(
derive_input.ident.span(),
"Unless `derive(ValueDelegate)` is used over a newtype with 1 field, \
the delegated type must be specified using \
#[value_delegate(from = chosen_type)]",
#[value_delegate(from = chosen_type)] \
or \
#[value_delegate(try_from = chosen_type)]",
)
})?;

Ok(ValueDelegateInput {
delegated_ty,
ident: derive_input.ident,
mode,
nullable: args.map(|a| a.nullable).unwrap_or(false),
nullable: args.nullable,
})
}
}
Expand All @@ -123,7 +134,7 @@ pub fn impl_value_delegate(input: ValueDelegateInput) -> syn::Result<proc_macro:

// this must be called in a context where `this` is defined.
let delegate_value = match mode {
DeriveMode::From => {
DeriveMode::From | DeriveMode::TryFrom => {
quote!(<#delegated_ty as std::convert::From<_>>::from(this))
}
DeriveMode::Private => quote!(this.0),
Expand All @@ -143,10 +154,16 @@ pub fn impl_value_delegate(input: ValueDelegateInput) -> syn::Result<proc_macro:
}
});

let ident_str = ident.to_string();
let from_value = match mode {
DeriveMode::From => {
quote!(#ident::from(<#delegated_ty as #crate_ident::value::FromValue<'a>>::from_value(value)))
}
DeriveMode::TryFrom => {
quote!(#ident::try_from(<#delegated_ty as #crate_ident::value::FromValue<'a>>::from_value(
value
)).unwrap_or_else(|e| panic!("Converting {} using TryFrom failed: {:?}", #ident_str, e)))
}
DeriveMode::Private => {
quote!(#ident(<#delegated_ty as #crate_ident::value::FromValue<'a>>::from_value(value)))
}
Expand Down
2 changes: 1 addition & 1 deletion glib-macros/tests/value_delegate_derive.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use glib::{value::FromValue, HasParamSpec, StaticType, ToValue};
use glib::{value::FromValue, StaticType, ToValue};

#[test]
fn higher_level_types() {
Expand Down