Skip to content

Commit 4e4054f

Browse files
ranfdevbilelmoussaoui
authored andcommitted
properties: Update syntax for custom flags and other builder fields
1 parent fbfffb5 commit 4e4054f

File tree

3 files changed

+92
-136
lines changed

3 files changed

+92
-136
lines changed

glib-macros/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -877,9 +877,9 @@ pub fn cstr_bytes(item: TokenStream) -> TokenStream {
877877
/// #[property(name = "author-name", get, set, type = String, member = name)]
878878
/// #[property(name = "author-nick", get, set, type = String, member = nick)]
879879
/// author: RefCell<Author>,
880-
/// #[property(get, set, user_1, user_2, lax_validation)]
880+
/// #[property(get, set, explicit_notify, lax_validation)]
881881
/// custom_flags: RefCell<String>,
882-
/// #[property(get, set, builder().minimum(0).maximum(5))]
882+
/// #[property(get, set, minimum = 0, maximum = 3)]
883883
/// numeric_builder: RefCell<u32>,
884884
/// #[property(get, set, builder('c'))]
885885
/// builder_with_required_param: RefCell<char>,

glib-macros/src/properties.rs

Lines changed: 74 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use proc_macro::TokenStream;
55
use proc_macro2::TokenStream as TokenStream2;
66
use quote::format_ident;
77
use quote::{quote, quote_spanned};
8-
use std::str::FromStr;
8+
use std::collections::HashMap;
99
use syn::ext::IdentExt;
1010
use syn::parenthesized;
1111
use syn::parse::Parse;
@@ -49,7 +49,7 @@ impl Parse for PropsMacroInput {
4949
_ => {
5050
return Err(syn::Error::new(
5151
derive_input.span(),
52-
"props can only be derived on structs",
52+
"Properties can only be derived on structs",
5353
))
5454
}
5555
};
@@ -76,12 +76,6 @@ impl std::convert::From<Option<syn::Expr>> for MaybeCustomFn {
7676
}
7777

7878
enum PropAttr {
79-
// ident
80-
Flag(&'static str),
81-
82-
// path
83-
FlagPath(syn::Path),
84-
8579
// builder(required_params).parameter(value)
8680
// becomes
8781
// Builder(Punctuated(required_params), Optionals(TokenStream))
@@ -94,62 +88,33 @@ enum PropAttr {
9488
// ident = expr
9589
Type(syn::Type),
9690

91+
// This will get translated from `ident = value` to `.ident(value)`
92+
// and will get appended after the `builder(...)` call.
93+
// ident [= expr]
94+
BuilderField((syn::Ident, Option<syn::Expr>)),
95+
9796
// ident = ident
9897
Member(syn::Ident),
9998

10099
// ident = "literal"
101100
Name(syn::LitStr),
102-
Nick(syn::LitStr),
103-
Blurb(syn::LitStr),
104101
}
105102

106-
const FLAGS: [&str; 16] = [
107-
"readable",
108-
"writable",
109-
"readwrite",
110-
"construct",
111-
"construct_only",
112-
"lax_validation",
113-
"user_1",
114-
"user_2",
115-
"user_3",
116-
"user_4",
117-
"user_5",
118-
"user_6",
119-
"user_7",
120-
"user_8",
121-
"explicit_notify",
122-
"deprecated",
123-
];
124103
impl Parse for PropAttr {
125104
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
126105
let name = input.call(syn::Ident::parse_any)?;
127106
let name_str = name.to_string();
128107

129108
let res = if input.peek(Token![=]) {
130109
let _assign_token: Token![=] = input.parse()?;
131-
if input.peek(syn::LitStr) {
132-
let lit: syn::LitStr = input.parse()?;
133-
// name = "literal"
134-
match &*name_str {
135-
"name" => PropAttr::Name(lit),
136-
"nick" => PropAttr::Nick(lit),
137-
"blurb" => PropAttr::Blurb(lit),
138-
_ => {
139-
panic!("Invalid attribute for property")
140-
}
141-
}
142-
} else {
143-
// name = expr | type | ident
144-
match &*name_str {
145-
"get" => PropAttr::Get(Some(input.parse()?)),
146-
"set" => PropAttr::Set(Some(input.parse()?)),
147-
"type" => PropAttr::Type(input.parse()?),
148-
"member" => PropAttr::Member(input.parse()?),
149-
_ => {
150-
panic!("Invalid attribute for property")
151-
}
152-
}
110+
// name = expr | type | ident
111+
match &*name_str {
112+
"name" => PropAttr::Name(input.parse()?),
113+
"get" => PropAttr::Get(Some(input.parse()?)),
114+
"set" => PropAttr::Set(Some(input.parse()?)),
115+
"type" => PropAttr::Type(input.parse()?),
116+
"member" => PropAttr::Member(input.parse()?),
117+
_ => PropAttr::BuilderField((name, Some(input.parse()?))),
153118
}
154119
} else if input.peek(syn::token::Paren) {
155120
match &*name_str {
@@ -160,31 +125,29 @@ impl Parse for PropAttr {
160125
let rest: TokenStream2 = input.parse()?;
161126
PropAttr::Builder(required, rest)
162127
}
163-
_ => panic!("Unsupported attribute list {name_str}(...)"),
128+
_ => {
129+
return Err(syn::Error::new(
130+
name.span(),
131+
format!("Unsupported attribute list {name_str}(...)"),
132+
))
133+
}
164134
}
165-
} else if input.peek(Token![::]) {
166-
let mut p: syn::Path = input.parse()?;
167-
p.segments.insert(
168-
0,
169-
syn::PathSegment {
170-
ident: format_ident!("{}", name),
171-
arguments: syn::PathArguments::None,
172-
},
173-
);
174-
PropAttr::FlagPath(p)
175135
} else {
176136
// attributes with only the identifier
177137
// name
178138
match &*name_str {
179139
"get" => PropAttr::Get(None),
180140
"set" => PropAttr::Set(None),
181-
name => {
182-
if let Some(flag) = FLAGS.iter().find(|x| *x == &name) {
183-
PropAttr::Flag(flag)
184-
} else {
185-
panic!("Invalid attribute for property")
186-
}
141+
"readwrite" | "read_only" | "write_only" => {
142+
return Err(syn::Error::new(
143+
name.span(),
144+
format!(
145+
"{name} is a flag managed by the Properties macro. \
146+
Use `get` and `set` to manage read and write access to a property",
147+
),
148+
))
187149
}
150+
_ => PropAttr::BuilderField((name, None)),
188151
}
189152
};
190153
Ok(res)
@@ -197,69 +160,73 @@ struct ReceivedAttrs {
197160
set: Option<MaybeCustomFn>,
198161
ty: Option<syn::Type>,
199162
member: Option<syn::Ident>,
200-
flags: Vec<&'static str>,
201-
flags_paths: Vec<syn::Path>,
202163
name: Option<syn::LitStr>,
203-
nick: Option<syn::LitStr>,
204-
blurb: Option<syn::LitStr>,
205164
builder: Option<(Punctuated<syn::Expr, Token![,]>, TokenStream2)>,
165+
builder_fields: HashMap<syn::Ident, Option<syn::Expr>>,
206166
}
207167
impl ReceivedAttrs {
208-
fn new(attrs: impl IntoIterator<Item = PropAttr>) -> Self {
209-
attrs.into_iter().fold(Self::default(), |mut this, attr| {
168+
fn new(
169+
attrs_span: &proc_macro2::Span,
170+
attrs: impl IntoIterator<Item = PropAttr>,
171+
) -> syn::Result<Self> {
172+
let this = attrs.into_iter().fold(Self::default(), |mut this, attr| {
210173
this.set_from_attr(attr);
211174
this
212-
})
175+
});
176+
177+
if this.get.is_none() && this.set.is_none() {
178+
return Err(syn::Error::new(
179+
*attrs_span,
180+
"No `get` or `set` specified: at least one is required.".to_string(),
181+
));
182+
}
183+
Ok(this)
213184
}
214185
fn set_from_attr(&mut self, attr: PropAttr) {
215186
match attr {
216187
PropAttr::Get(some_fn) => self.get = Some(some_fn.into()),
217188
PropAttr::Set(some_fn) => self.set = Some(some_fn.into()),
218189
PropAttr::Name(lit) => self.name = Some(lit),
219-
PropAttr::Nick(lit) => self.nick = Some(lit),
220-
PropAttr::Blurb(lit) => self.blurb = Some(lit),
221190
PropAttr::Type(ty) => self.ty = Some(ty),
222191
PropAttr::Member(member) => self.member = Some(member),
223-
PropAttr::Flag(flag) => self.flags.push(flag),
224-
PropAttr::FlagPath(flag) => self.flags_paths.push(flag),
225192
PropAttr::Builder(required_params, optionals) => {
226193
self.builder = Some((required_params, optionals))
227194
}
195+
PropAttr::BuilderField((ident, expr)) => {
196+
self.builder_fields.insert(ident, expr);
197+
}
228198
}
229199
}
230200
}
201+
202+
// It's a cleaned up version of `ReceivedAttrs` where some missing attributes get a default,
203+
// generated value.
231204
struct PropDesc {
232205
attrs_span: proc_macro2::Span,
233206
field_ident: syn::Ident,
234207
ty: syn::Type,
235208
name: syn::LitStr,
236-
nick: Option<syn::LitStr>,
237-
blurb: Option<syn::LitStr>,
238209
get: Option<MaybeCustomFn>,
239210
set: Option<MaybeCustomFn>,
240211
member: Option<syn::Ident>,
241-
flags: Vec<&'static str>,
242-
flags_paths: Vec<syn::Path>,
243212
builder: Option<(Punctuated<syn::Expr, Token![,]>, TokenStream2)>,
213+
builder_fields: HashMap<syn::Ident, Option<syn::Expr>>,
244214
}
245215
impl PropDesc {
246216
fn new(
247217
attrs_span: proc_macro2::Span,
248218
field_ident: syn::Ident,
249219
field_ty: syn::Type,
250220
attrs: ReceivedAttrs,
251-
) -> Self {
221+
) -> syn::Result<Self> {
252222
let ReceivedAttrs {
253223
get,
254224
set,
255225
ty,
256226
member,
257-
flags,
258227
name,
259-
nick,
260-
blurb,
261228
builder,
262-
flags_paths,
229+
builder_fields,
263230
} = attrs;
264231

265232
// Fill needed, but missing, attributes with calculated default values
@@ -272,20 +239,17 @@ impl PropDesc {
272239
let ty = ty.unwrap_or_else(|| field_ty.clone());
273240

274241
// Now that everything is set and safe, return the final proprety description
275-
Self {
242+
Ok(Self {
276243
attrs_span,
277244
field_ident,
278245
get,
279246
set,
280247
ty,
281248
member,
282-
flags,
283249
name,
284-
nick,
285-
blurb,
286250
builder,
287-
flags_paths,
288-
}
251+
builder_fields,
252+
})
289253
}
290254
}
291255

@@ -294,52 +258,39 @@ fn expand_properties_fn(props: &[PropDesc]) -> TokenStream2 {
294258
let crate_ident = crate_ident_new();
295259
let properties_build_phase = props.iter().map(|prop| {
296260
let PropDesc {
297-
ty,
298-
name,
299-
nick,
300-
blurb,
301-
builder,
302-
flags_paths,
303-
..
261+
ty, name, builder, ..
304262
} = prop;
305263

306-
let flags = {
307-
let write = prop.set.as_ref().map(|_| quote!(WRITABLE));
308-
let read = prop.get.as_ref().map(|_| quote!(READABLE));
309-
310-
let flags_iter = [write, read].into_iter().flatten().chain(
311-
prop.flags
312-
.iter()
313-
.map(|x| str::to_uppercase(x))
314-
.map(|f| TokenStream2::from_str(&f).unwrap()),
315-
);
316-
quote!(#crate_ident::ParamFlags::empty() #(| #crate_ident::ParamFlags::#flags_iter)* #(| #flags_paths)*)
264+
let rw_flags = match (&prop.get, &prop.set) {
265+
(Some(_), Some(_)) => quote!(.readwrite()),
266+
(Some(_), None) => quote!(.read_only()),
267+
(None, Some(_)) => quote!(.write_only()),
268+
(None, None) => unreachable!("No `get` or `set` specified"),
317269
};
318270

319271
let builder_call = builder
320272
.as_ref()
321273
.cloned()
322-
.map(|(mut required_params, opts)| {
274+
.map(|(mut required_params, chained_methods)| {
323275
let name_expr = syn::ExprLit {
324276
attrs: vec![],
325277
lit: syn::Lit::Str(name.to_owned()),
326278
};
327279
required_params.insert(0, name_expr.into());
328280
let required_params = required_params.iter();
329281

330-
quote!((#(#required_params,)*)#opts)
282+
quote!((#(#required_params,)*)#chained_methods)
331283
})
332284
.unwrap_or(quote!((#name)));
333285

334-
let build_nick = nick.as_ref().map(|x| quote!(.nick(#x)));
335-
let build_blurb = blurb.as_ref().map(|x| quote!(.blurb(#x)));
286+
let builder_fields = prop.builder_fields.iter().map(|(k, v)| quote!(.#k(#v)));
287+
336288
let span = prop.attrs_span;
337289
quote_spanned! {span=>
338290
<<#ty as #crate_ident::Property>::Value as #crate_ident::HasParamSpec>
339291
::param_spec_builder() #builder_call
340-
#build_nick
341-
#build_blurb
342-
.flags(#flags)
292+
#rw_flags
293+
#(#builder_fields)*
343294
.build()
344295
}
345296
});
@@ -473,12 +424,12 @@ fn parse_fields(fields: syn::Fields) -> syn::Result<Vec<PropDesc>> {
473424
let attrs = attrs.parse_args_with(
474425
syn::punctuated::Punctuated::<PropAttr, Token![,]>::parse_terminated,
475426
)?;
476-
Ok(PropDesc::new(
427+
PropDesc::new(
477428
span,
478429
ident.as_ref().unwrap().clone(),
479430
ty.clone(),
480-
ReceivedAttrs::new(attrs),
481-
))
431+
ReceivedAttrs::new(&span, attrs)?,
432+
)
482433
})
483434
})
484435
.collect::<syn::Result<_>>()
@@ -501,7 +452,8 @@ fn expand_getset_properties_impl(props: &[PropDesc]) -> TokenStream2 {
501452
self.property::<<#ty as #crate_ident::Property>::Value>(#name)
502453
})
503454
});
504-
let setter = (p.set.is_some() && !p.flags.contains(&"construct_only")).then(|| {
455+
let is_construct_only = p.builder_fields.iter().any(|(k, _)| *k == "construct_only");
456+
let setter = (p.set.is_some() && !is_construct_only).then(|| {
505457
let ident = format_ident!("set_{}", ident);
506458
quote!(pub fn #ident<'a>(&self, value: impl std::borrow::Borrow<<<#ty as #crate_ident::Property>::Value as #crate_ident::HasParamSpec>::SetValue>) {
507459
self.set_property_from_value(#name, &::std::convert::From::from(std::borrow::Borrow::borrow(&value)))

0 commit comments

Comments
 (0)