diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 38813fb9985d..f840cad66c50 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -1030,6 +1030,29 @@ pub fn derive_props(input: TokenStream) -> TokenStream { /// assert_eq!(convertedv.get::>(), Ok(myv)); /// let convertedv = None::.to_value(); /// assert_eq!(convertedv.get::>(), Ok(None::)); +/// +/// +/// // if the conversion can fail, use `try_from` +/// #[derive(ValueDelegate, Debug, PartialEq)] +/// #[value_delegate(try_from = u32)] +/// struct MyUnsigned(u16); +/// +/// impl TryFrom for MyUnsigned { +/// type Error = std::num::TryFromIntError; +/// fn try_from(value: u32) -> Result { +/// 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::().unwrap().0 as u32) /// ``` #[proc_macro_derive(ValueDelegate, attributes(value_delegate))] pub fn derive_value_delegate(input: TokenStream) -> TokenStream { diff --git a/glib-macros/src/value_delegate_derive.rs b/glib-macros/src/value_delegate_derive.rs index f151fe99a37d..3400fa886efe 100644 --- a/glib-macros/src/value_delegate_derive.rs +++ b/glib-macros/src/value_delegate_derive.rs @@ -8,6 +8,7 @@ use crate::utils::crate_ident_new; #[derive(Default, Debug, Clone)] enum DeriveMode { From, + TryFrom, #[default] Private, } @@ -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 { 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`", - )) + )), } } } @@ -45,6 +51,7 @@ impl Parse for Arg { struct Args { nullable: bool, from_path: Option, + try_from_path: Option, } impl Parse for Args { @@ -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, } } @@ -64,41 +72,44 @@ impl Parse for Args { impl Parse for ValueDelegateInput { fn parse(input: syn::parse::ParseStream) -> syn::Result { let derive_input: syn::DeriveInput = input.parse()?; - let args: Option = 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)]", ) })?; @@ -106,7 +117,7 @@ impl Parse for ValueDelegateInput { delegated_ty, ident: derive_input.ident, mode, - nullable: args.map(|a| a.nullable).unwrap_or(false), + nullable: args.nullable, }) } } @@ -123,7 +134,7 @@ pub fn impl_value_delegate(input: ValueDelegateInput) -> syn::Result { + DeriveMode::From | DeriveMode::TryFrom => { quote!(<#delegated_ty as std::convert::From<_>>::from(this)) } DeriveMode::Private => quote!(this.0), @@ -143,10 +154,16 @@ pub fn impl_value_delegate(input: ValueDelegateInput) -> syn::Result { 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))) } diff --git a/glib-macros/tests/value_delegate_derive.rs b/glib-macros/tests/value_delegate_derive.rs index 8f1a8522ec65..2dc46941cc45 100644 --- a/glib-macros/tests/value_delegate_derive.rs +++ b/glib-macros/tests/value_delegate_derive.rs @@ -1,4 +1,4 @@ -use glib::{value::FromValue, HasParamSpec, StaticType, ToValue}; +use glib::{value::FromValue, StaticType, ToValue}; #[test] fn higher_level_types() {