Skip to content

Commit 2b91ec4

Browse files
authored
Merge pull request #1000 from ranfdev/value_delegate
Add ValueDelegate macro
2 parents 13e3079 + ba274d2 commit 2b91ec4

File tree

4 files changed

+278
-11
lines changed

4 files changed

+278
-11
lines changed

glib-macros/src/lib.rs

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod object_interface_attribute;
1111
mod object_subclass_attribute;
1212
mod properties;
1313
mod shared_boxed_derive;
14+
mod value_delegate_derive;
1415
mod variant_derive;
1516

1617
mod utils;
@@ -866,19 +867,31 @@ pub fn cstr_bytes(item: TokenStream) -> TokenStream {
866867
///
867868
/// # Supported types
868869
/// Every type implementing the trait `Property` is supported.
869-
///
870-
/// If you want to support a custom type, you should consider implementing `Property` and
871-
/// `PropertyGet`. If your type supports interior mutability, you should implement also
872-
/// `PropertySet` and `PropertySetNested` if possible.
873-
///
874870
/// The type `Option<T>` is supported as a property only if `Option<T>` implements `ToValueOptional`.
875871
/// Optional types also require the `nullable` attribute: without it, the generated setter on the wrapper type
876872
/// will take `T` instead of `Option<T>`, preventing the user from ever calling the setter with a `None` value.
877-
/// If your type doesn't support `PropertySet`, you can't use the generated setter, but you can
878-
/// always define a custom one.
879873
///
880-
/// If you want to support a custom type with a custom `ParamSpec`, you should implement the trait
881-
/// `HasParamSpec` instead of `Property`.
874+
/// ## Adding support for custom types
875+
/// ### Types wrapping an existing `T: glib::value::ToValue + glib::HasParamSpec`
876+
/// If you have declared a newtype as
877+
/// ```rust
878+
/// struct MyInt(i32);
879+
/// ```
880+
/// you can use it as a property by deriving `glib::ValueDelegate`.
881+
///
882+
/// ### Types with inner mutability
883+
/// The trait `glib::Property` must be implemented.
884+
/// The traits `PropertyGet` and `PropertySet` should be implemented to enable the Properties macro
885+
/// to generate a default internal getter/setter.
886+
/// If possible, implementing `PropertySetNested` is preferred over `PropertySet`, because it
887+
/// enables this macro to access the contained type and provide access to its fields,
888+
/// using the `member = $structfield` syntax.
889+
///
890+
/// ### Types without `glib::HasParamSpec`
891+
/// If you have encountered a type `T: glib::value::ToValue`, inside the `gtk-rs` crate, which doesn't implement `HasParamSpec`,
892+
/// then it's a bug and you should report it.
893+
/// If you need to support a `ToValue` type with a `ParamSpec` not provided by `gtk-rs`, then you need to
894+
/// implement `glib::HasParamSpec` on that type.
882895
///
883896
/// # Example
884897
/// ```
@@ -963,3 +976,63 @@ pub fn derive_props(input: TokenStream) -> TokenStream {
963976
let input = parse_macro_input!(input as properties::PropsMacroInput);
964977
properties::impl_derive_props(input)
965978
}
979+
980+
/// # Example
981+
/// ```
982+
/// use glib::prelude::*;
983+
/// use glib::ValueDelegate;
984+
///
985+
/// #[derive(ValueDelegate, Debug, PartialEq)]
986+
/// struct MyInt(i32);
987+
///
988+
/// let myv = MyInt(2);
989+
/// let convertedv = myv.to_value();
990+
/// assert_eq!(convertedv.get::<MyInt>(), Ok(myv));
991+
///
992+
///
993+
/// #[derive(ValueDelegate, Debug, PartialEq)]
994+
/// #[value_delegate(from = u32)]
995+
/// enum MyEnum {
996+
/// Zero,
997+
/// NotZero(u32)
998+
/// }
999+
///
1000+
/// impl From<u32> for MyEnum {
1001+
/// fn from(v: u32) -> Self {
1002+
/// match v {
1003+
/// 0 => MyEnum::Zero,
1004+
/// x => MyEnum::NotZero(x)
1005+
/// }
1006+
/// }
1007+
/// }
1008+
/// impl<'a> From<&'a MyEnum> for u32 {
1009+
/// fn from(v: &'a MyEnum) -> Self {
1010+
/// match v {
1011+
/// MyEnum::Zero => 0,
1012+
/// MyEnum::NotZero(x) => *x
1013+
/// }
1014+
/// }
1015+
/// }
1016+
///
1017+
/// let myv = MyEnum::NotZero(34);
1018+
/// let convertedv = myv.to_value();
1019+
/// assert_eq!(convertedv.get::<MyEnum>(), Ok(myv));
1020+
///
1021+
///
1022+
/// // If you want your type to be usable inside an `Option`, you can derive `ToValueOptional`
1023+
/// // by adding `nullable` as follows
1024+
/// #[derive(ValueDelegate, Debug, PartialEq)]
1025+
/// #[value_delegate(nullable)]
1026+
/// struct MyString(String);
1027+
///
1028+
/// let myv = Some(MyString("Hello world".to_string()));
1029+
/// let convertedv = myv.to_value();
1030+
/// assert_eq!(convertedv.get::<Option<MyString>>(), Ok(myv));
1031+
/// let convertedv = None::<MyString>.to_value();
1032+
/// assert_eq!(convertedv.get::<Option<MyString>>(), Ok(None::<MyString>));
1033+
/// ```
1034+
#[proc_macro_derive(ValueDelegate, attributes(value_delegate))]
1035+
pub fn derive_value_delegate(input: TokenStream) -> TokenStream {
1036+
let input = parse_macro_input!(input as value_delegate_derive::ValueDelegateInput);
1037+
value_delegate_derive::impl_value_delegate(input).unwrap()
1038+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use quote::quote;
4+
use syn::{parse::Parse, Token};
5+
6+
use crate::utils::crate_ident_new;
7+
8+
#[derive(Default, Debug, Clone)]
9+
enum DeriveMode {
10+
From,
11+
#[default]
12+
Private,
13+
}
14+
15+
pub struct ValueDelegateInput {
16+
delegated_ty: syn::Path,
17+
ident: syn::Ident,
18+
mode: DeriveMode,
19+
nullable: bool,
20+
}
21+
22+
enum Arg {
23+
FromPath(syn::Path),
24+
Nullable,
25+
}
26+
27+
impl Parse for Arg {
28+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
29+
let argname: syn::Ident = input.parse()?;
30+
if argname == "nullable" {
31+
Ok(Arg::Nullable)
32+
} else if argname == "from" {
33+
let _eq: Token![=] = input.parse()?;
34+
Ok(Arg::FromPath(input.parse()?))
35+
} else {
36+
Err(syn::Error::new(
37+
input.span(),
38+
"expected `nullable` or `from`",
39+
))
40+
}
41+
}
42+
}
43+
44+
#[derive(Default)]
45+
struct Args {
46+
nullable: bool,
47+
from_path: Option<syn::Path>,
48+
}
49+
50+
impl Parse for Args {
51+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
52+
let args = syn::punctuated::Punctuated::<Arg, Token![,]>::parse_terminated(input)?;
53+
let mut this = Args::default();
54+
for a in args {
55+
match a {
56+
Arg::FromPath(p) => this.from_path = Some(p),
57+
Arg::Nullable => this.nullable = true,
58+
}
59+
}
60+
Ok(this)
61+
}
62+
}
63+
64+
impl Parse for ValueDelegateInput {
65+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
66+
let derive_input: syn::DeriveInput = input.parse()?;
67+
let args: Option<Args> = if let Some(attr) = derive_input
68+
.attrs
69+
.iter()
70+
.find(|x| x.path.is_ident("value_delegate"))
71+
{
72+
let args: Args = attr.parse_args()?;
73+
Some(args)
74+
} else {
75+
None
76+
};
77+
78+
let (delegated_ty, mode) =
79+
if let Some(path) = args.as_ref().and_then(|a| a.from_path.as_ref()) {
80+
(Some(path.clone()), DeriveMode::From)
81+
} else {
82+
let path = match derive_input.data {
83+
syn::Data::Struct(s) => match s.fields {
84+
syn::Fields::Unnamed(fields) if fields.unnamed.iter().count() == 1 => {
85+
fields.unnamed.into_iter().next().and_then(|x| match x.ty {
86+
syn::Type::Path(p) => Some(p.path),
87+
_ => None,
88+
})
89+
}
90+
_ => None,
91+
},
92+
_ => None,
93+
};
94+
(path, DeriveMode::Private)
95+
};
96+
let delegated_ty = delegated_ty.ok_or_else(|| {
97+
syn::Error::new(
98+
derive_input.ident.span(),
99+
"Unless `derive(ValueDelegate)` is used over a newtype with 1 field, \
100+
the delegated type must be specified using \
101+
#[value_delegate(from = chosen_type)]",
102+
)
103+
})?;
104+
105+
Ok(ValueDelegateInput {
106+
delegated_ty,
107+
ident: derive_input.ident,
108+
mode,
109+
nullable: args.map(|a| a.nullable).unwrap_or(false),
110+
})
111+
}
112+
}
113+
114+
pub fn impl_value_delegate(input: ValueDelegateInput) -> syn::Result<proc_macro::TokenStream> {
115+
let ValueDelegateInput {
116+
delegated_ty,
117+
ident,
118+
mode,
119+
nullable,
120+
..
121+
} = &input;
122+
let crate_ident = crate_ident_new();
123+
124+
// this must be called in a context where `this` is defined.
125+
let delegate_value = match mode {
126+
DeriveMode::From => quote!(#delegated_ty::from(this)),
127+
DeriveMode::Private => quote!(this.0),
128+
};
129+
130+
let to_value_optional = nullable.then(|| {
131+
quote! {
132+
impl #crate_ident::value::ToValueOptional for #ident {
133+
fn to_value_optional(s: Option<&Self>) -> #crate_ident::value::Value {
134+
if let Some(this) = s {
135+
Some(&#delegate_value).to_value()
136+
} else {
137+
#crate_ident::value::ToValueOptional::to_value_optional(None::<&#delegated_ty>)
138+
}
139+
}
140+
}
141+
}
142+
});
143+
144+
let from_value = match mode {
145+
DeriveMode::From => quote!(#ident::from(#delegated_ty::from_value(value))),
146+
DeriveMode::Private => quote!(#ident(#delegated_ty::from_value(value))),
147+
};
148+
149+
let res = quote! {
150+
impl #crate_ident::types::StaticType for #ident {
151+
fn static_type() -> glib::types::Type {
152+
#delegated_ty::static_type()
153+
}
154+
}
155+
impl #crate_ident::value::ToValue for #ident {
156+
fn to_value(&self) -> #crate_ident::value::Value {
157+
let this = self;
158+
#delegate_value.to_value()
159+
}
160+
fn value_type(&self) -> #crate_ident::types::Type {
161+
let this = self;
162+
#delegate_value.value_type()
163+
}
164+
}
165+
166+
#to_value_optional
167+
168+
unsafe impl<'a> #crate_ident::value::FromValue<'a> for #ident {
169+
type Checker = <#delegated_ty as #crate_ident::value::FromValue<'a>>::Checker;
170+
171+
unsafe fn from_value(value: &'a #crate_ident::value::Value) -> Self {
172+
#from_value
173+
}
174+
}
175+
176+
impl #crate_ident::HasParamSpec for #ident {
177+
type ParamSpec = <#delegated_ty as #crate_ident::HasParamSpec>::ParamSpec;
178+
type SetValue = Self;
179+
type BuilderFn = <#delegated_ty as #crate_ident::HasParamSpec>::BuilderFn;
180+
181+
fn param_spec_builder() -> Self::BuilderFn {
182+
#delegated_ty::param_spec_builder()
183+
}
184+
}
185+
};
186+
Ok(res.into())
187+
}

glib-macros/tests/properties.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ mod base {
6060
mod foo {
6161
use glib::prelude::*;
6262
use glib::subclass::prelude::*;
63-
use glib_macros::Properties;
63+
use glib_macros::{Properties, ValueDelegate};
6464
use once_cell::sync::OnceCell;
6565
use std::cell::Cell;
6666
use std::cell::RefCell;
@@ -69,6 +69,9 @@ mod foo {
6969

7070
use super::base::Base;
7171

72+
#[derive(ValueDelegate, Default, Debug, PartialEq)]
73+
pub struct MyPropertyValue(pub i32);
74+
7275
#[derive(Clone, Default, Debug, PartialEq, Eq, glib::Boxed)]
7376
#[boxed_type(name = "SimpleBoxedString")]
7477
pub struct SimpleBoxedString(pub String);
@@ -109,6 +112,8 @@ mod foo {
109112
// when the value is accessed.
110113
#[property(get = Self::hello_world)]
111114
_buzz: PhantomData<String>,
115+
#[property(get, set)]
116+
my_property_value: RefCell<MyPropertyValue>,
112117
#[property(get, set = Self::set_fizz, name = "fizz", nick = "fizz-nick",
113118
blurb = "short description stored in the GLib type system"
114119
)]
@@ -206,6 +211,8 @@ fn props() {
206211
assert_eq!(bar, "".to_string());
207212
let string_vec: Vec<String> = myfoo.property("string-vec");
208213
assert!(string_vec.is_empty());
214+
let my_property_value: foo::MyPropertyValue = myfoo.property("my-property-value");
215+
assert_eq!(my_property_value, foo::MyPropertyValue(0));
209216
let var: Option<glib::Variant> = myfoo.property("variant");
210217
assert!(var.is_none());
211218

glib/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub use ffi;
1111
pub use glib_macros::cstr_bytes;
1212
pub use glib_macros::{
1313
clone, closure, closure_local, flags, object_interface, object_subclass, Boxed, Downgrade,
14-
Enum, ErrorDomain, Properties, SharedBoxed, Variant,
14+
Enum, ErrorDomain, Properties, SharedBoxed, ValueDelegate, Variant,
1515
};
1616
pub use gobject_ffi;
1717
#[doc(hidden)]

0 commit comments

Comments
 (0)