1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use crate::utils::crate_ident_new; |
4 | use proc_macro::TokenStream; |
5 | use proc_macro2::TokenStream as TokenStream2; |
6 | use quote::format_ident; |
7 | use quote::{quote, quote_spanned}; |
8 | use std::collections::HashMap; |
9 | use syn::ext::IdentExt; |
10 | use syn::parenthesized; |
11 | use syn::parse::Parse; |
12 | use syn::punctuated::Punctuated; |
13 | use syn::spanned::Spanned; |
14 | use syn::Token; |
15 | use syn::{parse_quote_spanned, LitStr}; |
16 | |
17 | pub struct PropsMacroInput { |
18 | wrapper_ty: syn::Path, |
19 | ext_trait: Option<Option<syn::Ident>>, |
20 | ident: syn::Ident, |
21 | props: Vec<PropDesc>, |
22 | } |
23 | |
24 | pub struct PropertiesAttrs { |
25 | wrapper_ty: syn::Path, |
26 | // None => no ext trait, |
27 | // Some(None) => derive the ext trait from the wrapper type, |
28 | // Some(Some(ident)) => use the given ext trait Ident |
29 | ext_trait: Option<Option<syn::Ident>>, |
30 | } |
31 | |
32 | impl Parse for PropertiesAttrs { |
33 | fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
34 | let mut wrapper_ty = None; |
35 | let mut ext_trait = None; |
36 | |
37 | while !input.is_empty() { |
38 | let ident = input.parse::<syn::Ident>()?; |
39 | if ident == "wrapper_type" { |
40 | let _eq = input.parse::<Token![=]>()?; |
41 | wrapper_ty = Some(input.parse::<syn::Path>()?); |
42 | } else if ident == "ext_trait" { |
43 | if input.peek(Token![=]) { |
44 | let _eq = input.parse::<Token![=]>()?; |
45 | let ident = input.parse::<syn::Ident>()?; |
46 | ext_trait = Some(Some(ident)); |
47 | } else { |
48 | ext_trait = Some(None); |
49 | } |
50 | } |
51 | if input.peek(Token![,]) { |
52 | input.parse::<Token![,]>()?; |
53 | } |
54 | } |
55 | |
56 | Ok(Self { |
57 | wrapper_ty: wrapper_ty.ok_or_else(|| { |
58 | syn::Error::new(input.span(), "missing #[properties(wrapper_type = ...)]" ) |
59 | })?, |
60 | ext_trait, |
61 | }) |
62 | } |
63 | } |
64 | |
65 | impl Parse for PropsMacroInput { |
66 | fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
67 | let derive_input: syn::DeriveInput = input.parse()?; |
68 | let attrs = derive_input |
69 | .attrs |
70 | .iter() |
71 | .find(|x| x.path().is_ident("properties" )) |
72 | .ok_or_else(|| { |
73 | syn::Error::new( |
74 | derive_input.span(), |
75 | "missing #[properties(wrapper_type = ...)]" , |
76 | ) |
77 | })?; |
78 | let attrs: PropertiesAttrs = attrs.parse_args()?; |
79 | let props: Vec<_> = match derive_input.data { |
80 | syn::Data::Struct(struct_data) => parse_fields(struct_data.fields)?, |
81 | _ => { |
82 | return Err(syn::Error::new( |
83 | derive_input.span(), |
84 | "Properties can only be derived on structs" , |
85 | )) |
86 | } |
87 | }; |
88 | Ok(Self { |
89 | wrapper_ty: attrs.wrapper_ty, |
90 | ext_trait: attrs.ext_trait, |
91 | ident: derive_input.ident, |
92 | props, |
93 | }) |
94 | } |
95 | } |
96 | |
97 | enum MaybeCustomFn { |
98 | Custom(Box<syn::Expr>), |
99 | Default, |
100 | } |
101 | |
102 | impl std::convert::From<Option<syn::Expr>> for MaybeCustomFn { |
103 | fn from(item: Option<syn::Expr>) -> Self { |
104 | match item { |
105 | Some(expr: Expr) => Self::Custom(Box::new(expr)), |
106 | None => Self::Default, |
107 | } |
108 | } |
109 | } |
110 | |
111 | enum PropAttr { |
112 | // builder(required_params).parameter(value) |
113 | // becomes |
114 | // Builder(Punctuated(required_params), Optionals(TokenStream)) |
115 | Builder(Punctuated<syn::Expr, Token![,]>, TokenStream2), |
116 | |
117 | // ident |
118 | Nullable, |
119 | |
120 | // ident [= expr] |
121 | Get(Option<syn::Expr>), |
122 | Set(Option<syn::Expr>), |
123 | |
124 | // ident = expr |
125 | OverrideClass(syn::Type), |
126 | OverrideInterface(syn::Type), |
127 | |
128 | // ident = expr |
129 | Type(syn::Type), |
130 | |
131 | // This will get translated from `ident = value` to `.ident(value)` |
132 | // and will get appended after the `builder(...)` call. |
133 | // ident [= expr] |
134 | BuilderField((syn::Ident, Option<syn::Expr>)), |
135 | |
136 | // ident = ident |
137 | Member(syn::Ident), |
138 | |
139 | // ident = "literal" |
140 | Name(syn::LitStr), |
141 | } |
142 | |
143 | impl Parse for PropAttr { |
144 | fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
145 | let name = input.call(syn::Ident::parse_any)?; |
146 | let name_str = name.to_string(); |
147 | |
148 | let res = if input.peek(Token![=]) { |
149 | let _assign_token: Token![=] = input.parse()?; |
150 | // name = expr | type | ident |
151 | match &*name_str { |
152 | "name" => PropAttr::Name(input.parse()?), |
153 | "get" => PropAttr::Get(Some(input.parse()?)), |
154 | "set" => PropAttr::Set(Some(input.parse()?)), |
155 | "override_class" => PropAttr::OverrideClass(input.parse()?), |
156 | "override_interface" => PropAttr::OverrideInterface(input.parse()?), |
157 | "type" => PropAttr::Type(input.parse()?), |
158 | "member" => PropAttr::Member(input.parse()?), |
159 | // Special case "default = ..." and map it to .default_value(...) |
160 | "default" => PropAttr::BuilderField(( |
161 | syn::Ident::new("default_value" , name.span()), |
162 | Some(input.parse()?), |
163 | )), |
164 | _ => PropAttr::BuilderField((name, Some(input.parse()?))), |
165 | } |
166 | } else if input.peek(syn::token::Paren) { |
167 | match &*name_str { |
168 | "builder" => { |
169 | let content; |
170 | parenthesized!(content in input); |
171 | let required = content.parse_terminated(syn::Expr::parse, Token![,])?; |
172 | let rest: TokenStream2 = input.parse()?; |
173 | PropAttr::Builder(required, rest) |
174 | } |
175 | _ => { |
176 | return Err(syn::Error::new( |
177 | name.span(), |
178 | format!("Unsupported attribute list {name_str}(...)" ), |
179 | )) |
180 | } |
181 | } |
182 | } else { |
183 | // attributes with only the identifier name |
184 | match &*name_str { |
185 | "nullable" => PropAttr::Nullable, |
186 | "get" => PropAttr::Get(None), |
187 | "set" => PropAttr::Set(None), |
188 | "readwrite" | "read_only" | "write_only" => { |
189 | return Err(syn::Error::new( |
190 | name.span(), |
191 | format!( |
192 | " {name} is a flag managed by the Properties macro. \ |
193 | Use `get` and `set` to manage read and write access to a property" , |
194 | ), |
195 | )) |
196 | } |
197 | _ => PropAttr::BuilderField((name, None)), |
198 | } |
199 | }; |
200 | Ok(res) |
201 | } |
202 | } |
203 | |
204 | #[derive (Default)] |
205 | struct ReceivedAttrs { |
206 | nullable: bool, |
207 | get: Option<MaybeCustomFn>, |
208 | set: Option<MaybeCustomFn>, |
209 | override_class: Option<syn::Type>, |
210 | override_interface: Option<syn::Type>, |
211 | ty: Option<syn::Type>, |
212 | member: Option<syn::Ident>, |
213 | name: Option<syn::LitStr>, |
214 | builder: Option<(Punctuated<syn::Expr, Token![,]>, TokenStream2)>, |
215 | builder_fields: HashMap<syn::Ident, Option<syn::Expr>>, |
216 | } |
217 | |
218 | impl Parse for ReceivedAttrs { |
219 | fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
220 | let attrs: Punctuated = syn::punctuated::Punctuated::<PropAttr, Token![,]>::parse_terminated(input)?; |
221 | let this: ReceivedAttrs = attrs.into_iter().fold(Self::default(), |mut this: ReceivedAttrs, attr: PropAttr| { |
222 | this.set_from_attr(attr); |
223 | this |
224 | }); |
225 | |
226 | Ok(this) |
227 | } |
228 | } |
229 | |
230 | impl ReceivedAttrs { |
231 | fn set_from_attr(&mut self, attr: PropAttr) { |
232 | match attr { |
233 | PropAttr::Nullable => self.nullable = true, |
234 | PropAttr::Get(some_fn: Option) => self.get = Some(some_fn.into()), |
235 | PropAttr::Set(some_fn: Option) => self.set = Some(some_fn.into()), |
236 | PropAttr::Name(lit: LitStr) => self.name = Some(lit), |
237 | PropAttr::OverrideClass(ty: Type) => self.override_class = Some(ty), |
238 | PropAttr::OverrideInterface(ty: Type) => self.override_interface = Some(ty), |
239 | PropAttr::Type(ty: Type) => self.ty = Some(ty), |
240 | PropAttr::Member(member: Ident) => self.member = Some(member), |
241 | PropAttr::Builder(required_params: Punctuated, optionals: TokenStream) => { |
242 | self.builder = Some((required_params, optionals)) |
243 | } |
244 | PropAttr::BuilderField((ident: Ident, expr: Option)) => { |
245 | self.builder_fields.insert(k:ident, v:expr); |
246 | } |
247 | } |
248 | } |
249 | } |
250 | |
251 | // It's a cleaned up version of `ReceivedAttrs` where some missing attributes get a default, |
252 | // generated value. |
253 | struct PropDesc { |
254 | attrs_span: proc_macro2::Span, |
255 | field_ident: syn::Ident, |
256 | ty: syn::Type, |
257 | name: syn::LitStr, |
258 | override_class: Option<syn::Type>, |
259 | override_interface: Option<syn::Type>, |
260 | nullable: bool, |
261 | get: Option<MaybeCustomFn>, |
262 | set: Option<MaybeCustomFn>, |
263 | member: Option<syn::Ident>, |
264 | builder: Option<(Punctuated<syn::Expr, Token![,]>, TokenStream2)>, |
265 | builder_fields: HashMap<syn::Ident, Option<syn::Expr>>, |
266 | is_construct_only: bool, |
267 | } |
268 | |
269 | impl PropDesc { |
270 | fn new( |
271 | attrs_span: proc_macro2::Span, |
272 | field_ident: syn::Ident, |
273 | field_ty: syn::Type, |
274 | attrs: ReceivedAttrs, |
275 | ) -> syn::Result<Self> { |
276 | let ReceivedAttrs { |
277 | nullable, |
278 | get, |
279 | mut set, |
280 | override_class, |
281 | override_interface, |
282 | ty, |
283 | member, |
284 | name, |
285 | builder, |
286 | builder_fields, |
287 | } = attrs; |
288 | |
289 | let is_construct_only = builder_fields.iter().any(|(k, _)| *k == "construct_only" ); |
290 | if is_construct_only && set.is_none() { |
291 | // Insert a default internal setter automatically |
292 | set = Some(MaybeCustomFn::Default); |
293 | } |
294 | |
295 | if get.is_none() && set.is_none() { |
296 | return Err(syn::Error::new( |
297 | attrs_span, |
298 | "No `get` or `set` specified: at least one is required." .to_string(), |
299 | )); |
300 | } |
301 | |
302 | if override_class.is_some() && override_interface.is_some() { |
303 | return Err(syn::Error::new( |
304 | attrs_span, |
305 | "Both `override_class` and `override_interface` specified." .to_string(), |
306 | )); |
307 | } |
308 | |
309 | // Fill needed, but missing, attributes with calculated default values |
310 | let name = name.unwrap_or_else(|| { |
311 | syn::LitStr::new( |
312 | &field_ident.to_string().trim_matches('_' ).replace('_' , "-" ), |
313 | field_ident.span(), |
314 | ) |
315 | }); |
316 | let ty = ty.unwrap_or_else(|| field_ty.clone()); |
317 | |
318 | // Now that everything is set and safe, return the final proprety description |
319 | Ok(Self { |
320 | attrs_span, |
321 | field_ident, |
322 | ty, |
323 | name, |
324 | override_class, |
325 | override_interface, |
326 | nullable, |
327 | get, |
328 | set, |
329 | member, |
330 | builder, |
331 | builder_fields, |
332 | is_construct_only, |
333 | }) |
334 | } |
335 | } |
336 | |
337 | fn expand_param_spec(prop: &PropDesc) -> TokenStream2 { |
338 | let crate_ident = crate_ident_new(); |
339 | let PropDesc { |
340 | ty, name, builder, .. |
341 | } = prop; |
342 | let stripped_name = strip_raw_prefix_from_name(name); |
343 | |
344 | match (&prop.override_class, &prop.override_interface) { |
345 | (Some(c), None) => { |
346 | return quote!(#crate_ident::ParamSpecOverride::for_class::<#c>(#stripped_name)) |
347 | } |
348 | (None, Some(i)) => { |
349 | return quote!(#crate_ident::ParamSpecOverride::for_interface::<#i>(#stripped_name)) |
350 | } |
351 | (Some(_), Some(_)) => { |
352 | unreachable!("Both `override_class` and `override_interface` specified" ) |
353 | } |
354 | (None, None) => (), |
355 | }; |
356 | |
357 | let rw_flags = match (&prop.get, &prop.set) { |
358 | (Some(_), Some(_)) => quote!(.readwrite()), |
359 | (Some(_), None) => quote!(.read_only()), |
360 | (None, Some(_)) => quote!(.write_only()), |
361 | (None, None) => unreachable!("No `get` or `set` specified" ), |
362 | }; |
363 | |
364 | let builder_call = builder |
365 | .as_ref() |
366 | .cloned() |
367 | .map(|(mut required_params, chained_methods)| { |
368 | let name_expr = syn::ExprLit { |
369 | attrs: vec![], |
370 | lit: syn::Lit::Str(stripped_name.to_owned()), |
371 | }; |
372 | required_params.insert(0, name_expr.into()); |
373 | let required_params = required_params.iter(); |
374 | |
375 | quote!((#(#required_params,)*)#chained_methods) |
376 | }) |
377 | .unwrap_or(quote!((#stripped_name))); |
378 | |
379 | let builder_fields = prop.builder_fields.iter().map(|(k, v)| quote!(.#k(#v))); |
380 | |
381 | let span = prop.attrs_span; |
382 | quote_spanned! {span=> |
383 | <<#ty as #crate_ident::Property>::Value as #crate_ident::HasParamSpec> |
384 | ::param_spec_builder() #builder_call |
385 | #rw_flags |
386 | #(#builder_fields)* |
387 | .build() |
388 | } |
389 | } |
390 | |
391 | fn expand_properties_fn(props: &[PropDesc]) -> TokenStream2 { |
392 | let n_props: usize = props.len(); |
393 | let crate_ident: TokenStream = crate_ident_new(); |
394 | let param_specs: impl Iterator = props.iter().map(expand_param_spec); |
395 | quote!( |
396 | fn derived_properties() -> &'static [#crate_ident::ParamSpec] { |
397 | use #crate_ident::ParamSpecBuilderExt; |
398 | use #crate_ident::once_cell::sync::Lazy; |
399 | static PROPERTIES: Lazy<[#crate_ident::ParamSpec; #n_props]> = Lazy::new(|| [ |
400 | #(#param_specs,)* |
401 | ]); |
402 | PROPERTIES.as_ref() |
403 | } |
404 | ) |
405 | } |
406 | |
407 | fn expand_property_fn(props: &[PropDesc]) -> TokenStream2 { |
408 | let crate_ident = crate_ident_new(); |
409 | let match_branch_get = props.iter().flat_map(|p| { |
410 | let PropDesc { |
411 | name, |
412 | field_ident, |
413 | member, |
414 | get, |
415 | ty, |
416 | .. |
417 | } = p; |
418 | |
419 | let enum_ident = name_to_enum_ident(name.value()); |
420 | let span = p.attrs_span; |
421 | get.as_ref().map(|get| { |
422 | let body = match (member, get) { |
423 | (_, MaybeCustomFn::Custom(expr)) => quote!( |
424 | DerivedPropertiesEnum::#enum_ident => { |
425 | let value: <#ty as #crate_ident::Property>::Value = (#expr)(&self); |
426 | ::std::convert::From::from(value) |
427 | } |
428 | ), |
429 | (None, MaybeCustomFn::Default) => quote!( |
430 | DerivedPropertiesEnum::#enum_ident => |
431 | #crate_ident::PropertyGet::get(&self.#field_ident, |v| ::std::convert::From::from(v)) |
432 | |
433 | ), |
434 | (Some(member), MaybeCustomFn::Default) => quote!( |
435 | DerivedPropertiesEnum::#enum_ident => |
436 | #crate_ident::PropertyGet::get(&self.#field_ident, |v| ::std::convert::From::from(&v.#member)) |
437 | |
438 | ), |
439 | }; |
440 | quote_spanned!(span=> #body) |
441 | }) |
442 | }); |
443 | quote!( |
444 | fn derived_property( |
445 | &self, |
446 | id: usize, |
447 | pspec: &#crate_ident::ParamSpec |
448 | ) -> #crate_ident::Value { |
449 | let prop: DerivedPropertiesEnum = std::convert::TryFrom::try_from(id-1) |
450 | .unwrap_or_else(|_| panic!("property not defined {}" , pspec.name())); |
451 | match prop { |
452 | #(#match_branch_get,)* |
453 | _ => panic!("missing getter for property {}" , pspec.name()), |
454 | } |
455 | } |
456 | ) |
457 | } |
458 | |
459 | fn expand_set_property_fn(props: &[PropDesc]) -> TokenStream2 { |
460 | let crate_ident = crate_ident_new(); |
461 | let match_branch_set = props.iter().flat_map(|p| { |
462 | let PropDesc { |
463 | name, |
464 | field_ident, |
465 | member, |
466 | set, |
467 | ty, |
468 | .. |
469 | } = p; |
470 | let stripped_name = strip_raw_prefix_from_name(name); |
471 | let crate_ident = crate_ident_new(); |
472 | let enum_ident = name_to_enum_ident(name.value()); |
473 | let span = p.attrs_span; |
474 | let expect = quote!(.unwrap_or_else( |
475 | |err| panic!( |
476 | "Invalid conversion from `glib::value::Value` to `{}` inside setter for property `{}`: {:?}" , |
477 | ::std::any::type_name::<<#ty as #crate_ident::Property>::Value>(), #stripped_name, err |
478 | ) |
479 | )); |
480 | set.as_ref().map(|set| { |
481 | let body = match (member, set) { |
482 | (_, MaybeCustomFn::Custom(expr)) => quote!( |
483 | DerivedPropertiesEnum::#enum_ident => { |
484 | (#expr)(&self, #crate_ident::Value::get(value)#expect); |
485 | } |
486 | ), |
487 | (None, MaybeCustomFn::Default) => quote!( |
488 | DerivedPropertiesEnum::#enum_ident => { |
489 | #crate_ident::PropertySet::set( |
490 | &self.#field_ident, |
491 | #crate_ident::Value::get(value)#expect |
492 | ); |
493 | } |
494 | ), |
495 | (Some(member), MaybeCustomFn::Default) => quote!( |
496 | DerivedPropertiesEnum::#enum_ident => { |
497 | #crate_ident::PropertySetNested::set_nested( |
498 | &self.#field_ident, |
499 | move |v| v.#member = #crate_ident::Value::get(value)#expect |
500 | ); |
501 | } |
502 | ), |
503 | }; |
504 | quote_spanned!(span=> #body) |
505 | }) |
506 | }); |
507 | quote!( |
508 | fn derived_set_property(&self, |
509 | id: usize, |
510 | value: &#crate_ident::Value, |
511 | pspec: &#crate_ident::ParamSpec |
512 | ){ |
513 | let prop: DerivedPropertiesEnum = std::convert::TryFrom::try_from(id-1) |
514 | .unwrap_or_else(|_| panic!("property not defined {}" , pspec.name())); |
515 | match prop { |
516 | #(#match_branch_set,)* |
517 | _ => panic!("missing setter for property {}" , pspec.name()), |
518 | } |
519 | } |
520 | ) |
521 | } |
522 | |
523 | fn parse_fields(fields: syn::Fields) -> syn::Result<Vec<PropDesc>> { |
524 | fieldsimpl Iterator- >
|
525 | .into_iter() |
526 | .flat_map(|field: Field| { |
527 | let syn::Field { |
528 | ident: Option, attrs: Vec, ty: Type, .. |
529 | } = field; |
530 | attrsimpl Iterator |
531 | .into_iter() |
532 | .filter(|a: &Attribute| a.path().is_ident("property" )) |
533 | .map(move |prop_attrs: Attribute| { |
534 | let span: Span = prop_attrs.span(); |
535 | PropDesc::new( |
536 | attrs_span:span, |
537 | field_ident:ident.as_ref().unwrap().clone(), |
538 | field_ty:ty.clone(), |
539 | attrs:prop_attrs.parse_args()?, |
540 | ) |
541 | }) |
542 | }) |
543 | .collect::<syn::Result<_>>() |
544 | } |
545 | |
546 | /// Converts a glib property name to a correct rust ident |
547 | fn name_to_ident(name: &syn::LitStr) -> syn::Ident { |
548 | format_ident!(" {}" , name.value().replace('-' , "_" )) |
549 | } |
550 | |
551 | /// Strips out raw identifier prefix (`r#`) from literal string items |
552 | fn strip_raw_prefix_from_name(name: &LitStr) -> LitStr { |
553 | LitStr::new( |
554 | value:name.value().strip_prefix("r#" ).unwrap_or(&name.value()), |
555 | name.span(), |
556 | ) |
557 | } |
558 | |
559 | fn expand_impl_getset_properties(props: &[PropDesc]) -> Vec<syn::ImplItemFn> { |
560 | let crate_ident = crate_ident_new(); |
561 | let defs = props.iter().map(|p| { |
562 | let name = &p.name; |
563 | let stripped_name = strip_raw_prefix_from_name(name); |
564 | let ident = name_to_ident(name); |
565 | let ty = &p.ty; |
566 | |
567 | let getter = p.get.is_some().then(|| { |
568 | let span = p.attrs_span; |
569 | parse_quote_spanned!(span=> |
570 | #[must_use] |
571 | pub fn #ident(&self) -> <#ty as #crate_ident::Property>::Value { |
572 | self.property::<<#ty as #crate_ident::Property>::Value>(#stripped_name) |
573 | }) |
574 | }); |
575 | |
576 | let setter = (p.set.is_some() && !p.is_construct_only).then(|| { |
577 | let ident = format_ident!("set_ {}" , ident); |
578 | let target_ty = quote!(<<#ty as #crate_ident::Property>::Value as #crate_ident::HasParamSpec>::SetValue); |
579 | let set_ty = if p.nullable { |
580 | quote!(::core::option::Option<impl std::borrow::Borrow<#target_ty>>) |
581 | } else { |
582 | quote!(impl std::borrow::Borrow<#target_ty>) |
583 | }; |
584 | let upcasted_borrowed_value = if p.nullable { |
585 | quote!( |
586 | value.as_ref().map(|v| std::borrow::Borrow::borrow(v)) |
587 | ) |
588 | } else { |
589 | quote!( |
590 | std::borrow::Borrow::borrow(&value) |
591 | ) |
592 | }; |
593 | let span = p.attrs_span; |
594 | parse_quote_spanned!(span=> pub fn #ident<'a>(&self, value: #set_ty) { |
595 | self.set_property_from_value(#stripped_name, &::std::convert::From::from(#upcasted_borrowed_value)) |
596 | }) |
597 | }); |
598 | [getter, setter] |
599 | }); |
600 | defs.flatten() // flattens [] |
601 | .flatten() // removes None |
602 | .collect::<Vec<_>>() |
603 | } |
604 | |
605 | fn expand_impl_connect_prop_notify(props: &[PropDesc]) -> Vec<syn::ImplItemFn> { |
606 | let crate_ident: TokenStream = crate_ident_new(); |
607 | let connection_fns: impl Iterator = props.iter().map(|p: &PropDesc| -> syn::ImplItemFn { |
608 | let name: &LitStr = &p.name; |
609 | let stripped_name: LitStr = strip_raw_prefix_from_name(name); |
610 | let fn_ident: Ident = format_ident!("connect_ {}_notify" , name_to_ident(name)); |
611 | let span: Span = p.attrs_span; |
612 | parse_quote_spanned!(span=> pub fn #fn_ident<F: Fn(&Self) + 'static>(&self, f: F) -> #crate_ident::SignalHandlerId { |
613 | self.connect_notify_local(::core::option::Option::Some(#stripped_name), move |this, _| { |
614 | f(this) |
615 | }) |
616 | }) |
617 | }); |
618 | connection_fns.collect::<Vec<_>>() |
619 | } |
620 | |
621 | fn expand_impl_notify_prop(props: &[PropDesc]) -> Vec<syn::ImplItemFn> { |
622 | let crate_ident: TokenStream = crate_ident_new(); |
623 | let emit_fns: impl Iterator = props.iter().map(|p: &PropDesc| -> syn::ImplItemFn { |
624 | let name: LitStr = strip_raw_prefix_from_name(&p.name); |
625 | let fn_ident: Ident = format_ident!("notify_ {}" , name_to_ident(&name)); |
626 | let span: Span = p.attrs_span; |
627 | let enum_ident: Ident = name_to_enum_ident(name:name.value()); |
628 | parse_quote_spanned!(span=> pub fn #fn_ident(&self) { |
629 | self.notify_by_pspec( |
630 | &<<Self as #crate_ident::object::ObjectSubclassIs>::Subclass |
631 | as #crate_ident::subclass::object::DerivedObjectProperties>::derived_properties() |
632 | [DerivedPropertiesEnum::#enum_ident as usize] |
633 | ); |
634 | }) |
635 | }); |
636 | emit_fns.collect::<Vec<_>>() |
637 | } |
638 | |
639 | fn name_to_enum_ident(name: String) -> syn::Ident { |
640 | let mut name: String = name.strip_prefix("r#" ).unwrap_or(&name).to_owned(); |
641 | let mut slice: &mut str = name.as_mut_str(); |
642 | while let Some(i: usize) = slice.find('-' ) { |
643 | let (head: &mut str, tail: &mut str) = slice.split_at_mut(mid:i); |
644 | if let Some(c: &mut str) = head.get_mut(0..1) { |
645 | c.make_ascii_uppercase(); |
646 | } |
647 | slice = &mut tail[1..]; |
648 | } |
649 | if let Some(c: &mut str) = slice.get_mut(0..1) { |
650 | c.make_ascii_uppercase(); |
651 | } |
652 | let enum_member: String = name.split('-' ).collect(); |
653 | format_ident!(" {}" , enum_member) |
654 | } |
655 | |
656 | fn expand_properties_enum(props: &[PropDesc]) -> TokenStream2 { |
657 | let properties: Vec<syn::Ident> = props |
658 | .iter() |
659 | .map(|p| { |
660 | let name: String = p.name.value(); |
661 | |
662 | name_to_enum_ident(name) |
663 | }) |
664 | .collect(); |
665 | let props = properties.iter(); |
666 | let indices = 0..properties.len(); |
667 | quote! { |
668 | #[repr(usize)] |
669 | #[derive(Debug, Copy, Clone)] |
670 | enum DerivedPropertiesEnum { |
671 | #(#props,)* |
672 | } |
673 | impl std::convert::TryFrom<usize> for DerivedPropertiesEnum { |
674 | type Error = usize; |
675 | |
676 | fn try_from(item: usize) -> ::core::result::Result<Self, <Self as std::convert::TryFrom<usize>>::Error> { |
677 | match item { |
678 | #(#indices => ::core::result::Result::Ok(Self::#properties),)* |
679 | _ => ::core::result::Result::Err(item) |
680 | } |
681 | } |
682 | } |
683 | } |
684 | } |
685 | |
686 | pub fn impl_derive_props(input: PropsMacroInput) -> TokenStream { |
687 | let struct_ident = &input.ident; |
688 | let crate_ident = crate_ident_new(); |
689 | let wrapper_type = input.wrapper_ty; |
690 | let fn_properties = expand_properties_fn(&input.props); |
691 | let fn_property = expand_property_fn(&input.props); |
692 | let fn_set_property = expand_set_property_fn(&input.props); |
693 | let getset_properties = expand_impl_getset_properties(&input.props); |
694 | let connect_prop_notify = expand_impl_connect_prop_notify(&input.props); |
695 | let notify_prop = expand_impl_notify_prop(&input.props); |
696 | let properties_enum = expand_properties_enum(&input.props); |
697 | |
698 | let rust_interface = if let Some(ext_trait) = input.ext_trait { |
699 | let trait_ident = if let Some(ext_trait) = ext_trait { |
700 | ext_trait |
701 | } else { |
702 | format_ident!( |
703 | " {}PropertiesExt" , |
704 | wrapper_type.segments.last().unwrap().ident |
705 | ) |
706 | }; |
707 | let signatures = getset_properties |
708 | .iter() |
709 | .chain(connect_prop_notify.iter()) |
710 | .chain(notify_prop.iter()) |
711 | .map(|item| &item.sig); |
712 | let trait_def = quote! { |
713 | pub trait #trait_ident { |
714 | #(#signatures;)* |
715 | } |
716 | }; |
717 | let impls = getset_properties |
718 | .into_iter() |
719 | .chain(connect_prop_notify) |
720 | .chain(notify_prop) |
721 | .map(|mut item| { |
722 | item.vis = syn::Visibility::Inherited; |
723 | item |
724 | }); |
725 | quote! { |
726 | #trait_def |
727 | impl #trait_ident for #wrapper_type { |
728 | #(#impls)* |
729 | } |
730 | } |
731 | } else { |
732 | quote! { |
733 | #[allow(dead_code)] |
734 | impl #wrapper_type { |
735 | #(#getset_properties)* |
736 | #(#connect_prop_notify)* |
737 | #(#notify_prop)* |
738 | } |
739 | } |
740 | }; |
741 | |
742 | let expanded = quote! { |
743 | use #crate_ident::{PropertyGet, PropertySet, ToValue}; |
744 | |
745 | #properties_enum |
746 | |
747 | impl #crate_ident::subclass::object::DerivedObjectProperties for #struct_ident { |
748 | #fn_properties |
749 | #fn_property |
750 | #fn_set_property |
751 | } |
752 | |
753 | #rust_interface |
754 | }; |
755 | proc_macro::TokenStream::from(expanded) |
756 | } |
757 | |