Skip to content

Commit

Permalink
Add ValueDelegate macro
Browse files Browse the repository at this point in the history
  • Loading branch information
ranfdev committed Feb 20, 2023
1 parent a635857 commit ef7c865
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 11 deletions.
91 changes: 82 additions & 9 deletions glib-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod object_interface_attribute;
mod object_subclass_attribute;
mod properties;
mod shared_boxed_derive;
mod value_delegate_derive;
mod variant_derive;

mod utils;
Expand Down Expand Up @@ -866,17 +867,29 @@ pub fn cstr_bytes(item: TokenStream) -> TokenStream {
///
/// # Supported types
/// Every type implementing the trait `Property` is supported.
///
/// If you want to support a custom type, you should consider implementing `Property` and
/// `PropertyGet`. If your type supports interior mutability, you should implement also
/// `PropertySet` and `PropertySetNested` if possible.
///
/// The type `Option<T>` is supported as a property only if `Option<T>` implements `ToValueOptional`.
/// If your type doesn't support `PropertySet`, you can't use the generated setter, but you can
/// always define a custom one.
///
/// If you want to support a custom type with a custom `ParamSpec`, you should implement the trait
/// `HasParamSpec` instead of `Property`.
/// ## Adding support for custom types
/// ### Types wrapping an existing `T: glib::value::ToValue + glib::HasParamSpec`
/// If you have declared a newtype as
/// ```rust
/// struct MyInt(i32);
/// ```
/// you can use it as a property by deriving `glib::ValueDelegate`.
///
/// ### Types with inner mutability
/// The trait `glib::Property` must be implemented.
/// The traits `PropertyGet` and `PropertySet` should be implemented to enable the Properties macro
/// to generate a default internal getter/setter.
/// If possible, implementing `PropertySetNested` is preferred over `PropertySet`, because it
/// enables this macro to access the contained type and provide access to its fields,
/// using the `member = $structfield` syntax.
///
/// ### Types without `glib::HasParamSpec`
/// If you have encountered a type `T: glib::value::ToValue`, inside the `gtk-rs` crate, which doesn't implement `HasParamSpec`,
/// then it's a bug and you should report it.
/// If you need to support a `ToValue` type with a `ParamSpec` not provided by `gtk-rs`, then you need to
/// implement `glib::HasParamSpec` on that type.
///
/// # Example
/// ```
Expand Down Expand Up @@ -961,3 +974,63 @@ pub fn derive_props(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as properties::PropsMacroInput);
properties::impl_derive_props(input)
}

/// # Example
/// ```
/// use glib::prelude::*;
/// use glib::ValueDelegate;
///
/// #[derive(ValueDelegate, Debug, PartialEq)]
/// struct MyInt(i32);
///
/// let myv = MyInt(2);
/// let convertedv = myv.to_value();
/// assert_eq!(convertedv.get::<MyInt>(), Ok(myv));
///
///
/// #[derive(ValueDelegate, Debug, PartialEq)]
/// #[value_delegate(from = u32)]
/// enum MyEnum {
/// Zero,
/// NotZero(u32)
/// }
///
/// impl From<u32> for MyEnum {
/// fn from(v: u32) -> Self {
/// match v {
/// 0 => MyEnum::Zero,
/// x => MyEnum::NotZero(x)
/// }
/// }
/// }
/// impl<'a> From<&'a MyEnum> for u32 {
/// fn from(v: &'a MyEnum) -> Self {
/// match v {
/// MyEnum::Zero => 0,
/// MyEnum::NotZero(x) => *x
/// }
/// }
/// }
///
/// let myv = MyEnum::NotZero(34);
/// let convertedv = myv.to_value();
/// assert_eq!(convertedv.get::<MyEnum>(), Ok(myv));
///
///
/// // If you want your type to be usable inside an `Option`, you can derive `ToValueOptional`
/// // by adding `nullable` as follows
/// #[derive(ValueDelegate, Debug, PartialEq)]
/// #[value_delegate(nullable)]
/// struct MyString(String);
///
/// let myv = Some(MyString("Hello world".to_string()));
/// let convertedv = myv.to_value();
/// assert_eq!(convertedv.get::<Option<MyString>>(), Ok(myv));
/// let convertedv = None::<MyString>.to_value();
/// assert_eq!(convertedv.get::<Option<MyString>>(), Ok(None::<MyString>));
/// ```
#[proc_macro_derive(ValueDelegate, attributes(value_delegate))]
pub fn derive_value_delegate(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as value_delegate_derive::ValueDelegateInput);
value_delegate_derive::impl_value_delegate(input).unwrap()
}
187 changes: 187 additions & 0 deletions glib-macros/src/value_delegate_derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use quote::quote;
use syn::{parse::Parse, Token};

use crate::utils::crate_ident_new;

#[derive(Default, Debug, Clone)]
enum DeriveMode {
From,
#[default]
Private,
}

pub struct ValueDelegateInput {
delegated_ty: syn::Path,
ident: syn::Ident,
mode: DeriveMode,
nullable: bool,
}

enum Arg {
FromPath(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(
input.span(),
"expected `nullable` or `from`",
))
}
}
}

#[derive(Default)]
struct Args {
nullable: bool,
from_path: Option<syn::Path>,
}

impl Parse for Args {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let args = syn::punctuated::Punctuated::<Arg, Token![,]>::parse_terminated(input)?;
let mut this = Args::default();
for a in args {
match a {
Arg::FromPath(p) => this.from_path = Some(p),
Arg::Nullable => this.nullable = true,
}
}
Ok(this)
}
}

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
.attrs
.iter()
.find(|x| x.path.is_ident("value_delegate"))
{
let args: Args = attr.parse_args()?;
Some(args)
} else {
None
};

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,
},
_ => 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)]",
)
})?;

Ok(ValueDelegateInput {
delegated_ty,
ident: derive_input.ident,
mode,
nullable: args.map(|a| a.nullable).unwrap_or(false),
})
}
}

pub fn impl_value_delegate(input: ValueDelegateInput) -> syn::Result<proc_macro::TokenStream> {
let ValueDelegateInput {
delegated_ty,
ident,
mode,
nullable,
..
} = &input;
let crate_ident = crate_ident_new();

// this must be called in a context where `this` is defined.
let delegate_value = match mode {
DeriveMode::From => quote!(#delegated_ty::from(this)),
DeriveMode::Private => quote!(this.0),
};

let to_value_optional = nullable.then(|| {
quote! {
impl #crate_ident::value::ToValueOptional for #ident {
fn to_value_optional(s: Option<&Self>) -> #crate_ident::value::Value {
if let Some(this) = s {
Some(&#delegate_value).to_value()
} else {
#crate_ident::value::ToValueOptional::to_value_optional(None::<&#delegated_ty>)
}
}
}
}
});

let from_value = match mode {
DeriveMode::From => quote!(#ident::from(#delegated_ty::from_value(value))),
DeriveMode::Private => quote!(#ident(#delegated_ty::from_value(value))),
};

let res = quote! {
impl #crate_ident::types::StaticType for #ident {
fn static_type() -> glib::types::Type {
#delegated_ty::static_type()
}
}
impl #crate_ident::value::ToValue for #ident {
fn to_value(&self) -> #crate_ident::value::Value {
let this = self;
#delegate_value.to_value()
}
fn value_type(&self) -> #crate_ident::types::Type {
let this = self;
#delegate_value.value_type()
}
}

#to_value_optional

unsafe impl<'a> #crate_ident::value::FromValue<'a> for #ident {
type Checker = <#delegated_ty as #crate_ident::value::FromValue<'a>>::Checker;

unsafe fn from_value(value: &'a #crate_ident::value::Value) -> Self {
#from_value
}
}

impl #crate_ident::HasParamSpec for #ident {
type ParamSpec = <#delegated_ty as #crate_ident::HasParamSpec>::ParamSpec;
type SetValue = Self;
type BuilderFn = <#delegated_ty as #crate_ident::HasParamSpec>::BuilderFn;

fn param_spec_builder() -> Self::BuilderFn {
#delegated_ty::param_spec_builder()
}
}
};
Ok(res.into())
}
9 changes: 8 additions & 1 deletion glib-macros/tests/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ mod base {
mod foo {
use glib::prelude::*;
use glib::subclass::prelude::*;
use glib_macros::Properties;
use glib_macros::{Properties, ValueDelegate};
use once_cell::sync::OnceCell;
use std::cell::Cell;
use std::cell::RefCell;
Expand All @@ -69,6 +69,9 @@ mod foo {

use super::base::Base;

#[derive(ValueDelegate, Default, Debug, PartialEq)]
pub struct MyPropertyValue(pub i32);

#[derive(Clone, Default, Debug, PartialEq, Eq, glib::Boxed)]
#[boxed_type(name = "SimpleBoxedString")]
pub struct SimpleBoxedString(pub String);
Expand Down Expand Up @@ -107,6 +110,8 @@ mod foo {
// when the value is accessed.
#[property(get = Self::hello_world)]
_buzz: PhantomData<String>,
#[property(get, set)]
my_property_value: RefCell<MyPropertyValue>,
#[property(get, set = Self::set_fizz, name = "fizz", nick = "fizz-nick",
blurb = "short description stored in the GLib type system"
)]
Expand Down Expand Up @@ -204,6 +209,8 @@ fn props() {
assert_eq!(bar, "".to_string());
let string_vec: Vec<String> = myfoo.property("string-vec");
assert!(string_vec.is_empty());
let my_property_value: foo::MyPropertyValue = myfoo.property("my-property-value");
assert_eq!(my_property_value, foo::MyPropertyValue(0));

// Set values
myfoo.set_property("bar", "epic".to_value());
Expand Down
2 changes: 1 addition & 1 deletion glib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub use ffi;
pub use glib_macros::cstr_bytes;
pub use glib_macros::{
clone, closure, closure_local, flags, object_interface, object_subclass, Boxed, Downgrade,
Enum, ErrorDomain, Properties, SharedBoxed, Variant,
Enum, ErrorDomain, Properties, SharedBoxed, ValueDelegate, Variant,
};
pub use gobject_ffi;
#[doc(hidden)]
Expand Down

0 comments on commit ef7c865

Please sign in to comment.