| 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 property 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 | fn is_overriding(&self) -> bool { |
| 336 | self.override_class.is_some() || self.override_interface.is_some() |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | fn expand_param_spec(prop: &PropDesc) -> TokenStream2 { |
| 341 | let crate_ident = crate_ident_new(); |
| 342 | let PropDesc { |
| 343 | ty, name, builder, .. |
| 344 | } = prop; |
| 345 | let stripped_name = strip_raw_prefix_from_name(name); |
| 346 | |
| 347 | match (&prop.override_class, &prop.override_interface) { |
| 348 | (Some(c), None) => { |
| 349 | return quote!(#crate_ident::ParamSpecOverride::for_class::<#c>(#stripped_name)) |
| 350 | } |
| 351 | (None, Some(i)) => { |
| 352 | return quote!(#crate_ident::ParamSpecOverride::for_interface::<#i>(#stripped_name)) |
| 353 | } |
| 354 | (Some(_), Some(_)) => { |
| 355 | unreachable!("Both `override_class` and `override_interface` specified" ) |
| 356 | } |
| 357 | (None, None) => (), |
| 358 | }; |
| 359 | |
| 360 | let rw_flags = match (&prop.get, &prop.set) { |
| 361 | (Some(_), Some(_)) => quote!(.readwrite()), |
| 362 | (Some(_), None) => quote!(.read_only()), |
| 363 | (None, Some(_)) => quote!(.write_only()), |
| 364 | (None, None) => unreachable!("No `get` or `set` specified" ), |
| 365 | }; |
| 366 | |
| 367 | let builder_call = builder |
| 368 | .as_ref() |
| 369 | .cloned() |
| 370 | .map(|(mut required_params, chained_methods)| { |
| 371 | let name_expr = syn::ExprLit { |
| 372 | attrs: vec![], |
| 373 | lit: syn::Lit::Str(stripped_name.to_owned()), |
| 374 | }; |
| 375 | required_params.insert(0, name_expr.into()); |
| 376 | let required_params = required_params.iter(); |
| 377 | |
| 378 | quote!((#(#required_params,)*)#chained_methods) |
| 379 | }) |
| 380 | .unwrap_or(quote!((#stripped_name))); |
| 381 | |
| 382 | let builder_fields = prop.builder_fields.iter().map(|(k, v)| quote!(.#k(#v))); |
| 383 | |
| 384 | let span = prop.attrs_span; |
| 385 | quote_spanned! {span=> |
| 386 | <<#ty as #crate_ident::property::Property>::Value as #crate_ident::prelude::HasParamSpec> |
| 387 | ::param_spec_builder() #builder_call |
| 388 | #rw_flags |
| 389 | #(#builder_fields)* |
| 390 | .build() |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | fn expand_properties_fn(props: &[PropDesc]) -> TokenStream2 { |
| 395 | let n_props: usize = props.len(); |
| 396 | let crate_ident: TokenStream = crate_ident_new(); |
| 397 | let param_specs: impl Iterator = props.iter().map(expand_param_spec); |
| 398 | quote!( |
| 399 | fn derived_properties() -> &'static [#crate_ident::ParamSpec] { |
| 400 | use #crate_ident::prelude::ParamSpecBuilderExt; |
| 401 | static PROPERTIES: ::std::sync::OnceLock<[#crate_ident::ParamSpec; #n_props]> = ::std::sync::OnceLock::new(); |
| 402 | PROPERTIES.get_or_init(|| [ |
| 403 | #(#param_specs,)* |
| 404 | ]) |
| 405 | } |
| 406 | ) |
| 407 | } |
| 408 | |
| 409 | fn expand_property_fn(props: &[PropDesc]) -> TokenStream2 { |
| 410 | let crate_ident = crate_ident_new(); |
| 411 | let match_branch_get = props.iter().flat_map(|p| { |
| 412 | let PropDesc { |
| 413 | name, |
| 414 | field_ident, |
| 415 | member, |
| 416 | get, |
| 417 | ty, |
| 418 | .. |
| 419 | } = p; |
| 420 | |
| 421 | let enum_ident = name_to_enum_ident(name.value()); |
| 422 | let span = p.attrs_span; |
| 423 | get.as_ref().map(|get| { |
| 424 | let body = match (member, get) { |
| 425 | (_, MaybeCustomFn::Custom(expr)) => quote!( |
| 426 | DerivedPropertiesEnum::#enum_ident => { |
| 427 | let value: <#ty as #crate_ident::property::Property>::Value = (#expr)(&self); |
| 428 | ::std::convert::From::from(value) |
| 429 | } |
| 430 | ), |
| 431 | (None, MaybeCustomFn::Default) => quote!( |
| 432 | DerivedPropertiesEnum::#enum_ident => |
| 433 | #crate_ident::property::PropertyGet::get(&self.#field_ident, |v| ::std::convert::From::from(v)) |
| 434 | |
| 435 | ), |
| 436 | (Some(member), MaybeCustomFn::Default) => quote!( |
| 437 | DerivedPropertiesEnum::#enum_ident => |
| 438 | #crate_ident::property::PropertyGet::get(&self.#field_ident, |v| ::std::convert::From::from(&v.#member)) |
| 439 | |
| 440 | ), |
| 441 | }; |
| 442 | quote_spanned!(span=> #body) |
| 443 | }) |
| 444 | }); |
| 445 | quote!( |
| 446 | fn derived_property( |
| 447 | &self, |
| 448 | id: usize, |
| 449 | pspec: &#crate_ident::ParamSpec |
| 450 | ) -> #crate_ident::Value { |
| 451 | let prop: DerivedPropertiesEnum = std::convert::TryFrom::try_from(id-1) |
| 452 | .unwrap_or_else(|_| panic!("property not defined {}" , pspec.name())); |
| 453 | match prop { |
| 454 | #(#match_branch_get,)* |
| 455 | _ => panic!("missing getter for property {}" , pspec.name()), |
| 456 | } |
| 457 | } |
| 458 | ) |
| 459 | } |
| 460 | |
| 461 | fn expand_set_property_fn(props: &[PropDesc]) -> TokenStream2 { |
| 462 | let crate_ident = crate_ident_new(); |
| 463 | let match_branch_set = props.iter().flat_map(|p| { |
| 464 | let PropDesc { |
| 465 | name, |
| 466 | field_ident, |
| 467 | member, |
| 468 | set, |
| 469 | ty, |
| 470 | .. |
| 471 | } = p; |
| 472 | let stripped_name = strip_raw_prefix_from_name(name); |
| 473 | let crate_ident = crate_ident_new(); |
| 474 | let enum_ident = name_to_enum_ident(name.value()); |
| 475 | let span = p.attrs_span; |
| 476 | let expect = quote!(.unwrap_or_else( |
| 477 | |err| panic!( |
| 478 | "Invalid conversion from `glib::value::Value` to `{}` inside setter for property `{}`: {:?}" , |
| 479 | ::std::any::type_name::<<#ty as #crate_ident::property::Property>::Value>(), #stripped_name, err |
| 480 | ) |
| 481 | )); |
| 482 | set.as_ref().map(|set| { |
| 483 | let body = match (member, set) { |
| 484 | (_, MaybeCustomFn::Custom(expr)) => quote!( |
| 485 | DerivedPropertiesEnum::#enum_ident => { |
| 486 | (#expr)(&self, #crate_ident::Value::get(value)#expect); |
| 487 | } |
| 488 | ), |
| 489 | (None, MaybeCustomFn::Default) => quote!( |
| 490 | DerivedPropertiesEnum::#enum_ident => { |
| 491 | #crate_ident::property::PropertySet::set( |
| 492 | &self.#field_ident, |
| 493 | #crate_ident::Value::get(value)#expect |
| 494 | ); |
| 495 | } |
| 496 | ), |
| 497 | (Some(member), MaybeCustomFn::Default) => quote!( |
| 498 | DerivedPropertiesEnum::#enum_ident => { |
| 499 | #crate_ident::property::PropertySetNested::set_nested( |
| 500 | &self.#field_ident, |
| 501 | move |v| v.#member = #crate_ident::Value::get(value)#expect |
| 502 | ); |
| 503 | } |
| 504 | ), |
| 505 | }; |
| 506 | quote_spanned!(span=> #body) |
| 507 | }) |
| 508 | }); |
| 509 | quote!( |
| 510 | #[allow(unreachable_code)] |
| 511 | fn derived_set_property(&self, |
| 512 | id: usize, |
| 513 | value: &#crate_ident::Value, |
| 514 | pspec: &#crate_ident::ParamSpec |
| 515 | ){ |
| 516 | let prop: DerivedPropertiesEnum = std::convert::TryFrom::try_from(id-1) |
| 517 | .unwrap_or_else(|_| panic!("property not defined {}" , pspec.name())); |
| 518 | match prop { |
| 519 | #(#match_branch_set,)* |
| 520 | _ => panic!("missing setter for property {}" , pspec.name()), |
| 521 | } |
| 522 | } |
| 523 | ) |
| 524 | } |
| 525 | |
| 526 | fn parse_fields(fields: syn::Fields) -> syn::Result<Vec<PropDesc>> { |
| 527 | fieldsimpl Iterator- >
|
| 528 | .into_iter() |
| 529 | .flat_map(|field: Field| { |
| 530 | let syn::Field { |
| 531 | ident: Option, attrs: Vec, ty: Type, .. |
| 532 | } = field; |
| 533 | attrsimpl Iterator |
| 534 | .into_iter() |
| 535 | .filter(|a: &Attribute| a.path().is_ident("property" )) |
| 536 | .map(move |prop_attrs: Attribute| { |
| 537 | let span: Span = prop_attrs.span(); |
| 538 | PropDesc::new( |
| 539 | span, |
| 540 | field_ident:ident.as_ref().unwrap().clone(), |
| 541 | ty.clone(), |
| 542 | attrs:prop_attrs.parse_args()?, |
| 543 | ) |
| 544 | }) |
| 545 | }) |
| 546 | .collect::<syn::Result<_>>() |
| 547 | } |
| 548 | |
| 549 | /// Converts a glib property name to a correct rust ident |
| 550 | fn name_to_ident(name: &syn::LitStr) -> syn::Ident { |
| 551 | format_ident!(" {}" , name.value().replace('-' , "_" )) |
| 552 | } |
| 553 | |
| 554 | /// Strips out raw identifier prefix (`r#`) from literal string items |
| 555 | fn strip_raw_prefix_from_name(name: &LitStr) -> LitStr { |
| 556 | LitStr::new( |
| 557 | value:name.value().strip_prefix("r#" ).unwrap_or(&name.value()), |
| 558 | name.span(), |
| 559 | ) |
| 560 | } |
| 561 | |
| 562 | fn expand_impl_getset_properties(props: &[PropDesc]) -> Vec<syn::ImplItemFn> { |
| 563 | let crate_ident = crate_ident_new(); |
| 564 | let defs = props.iter().filter(|p| !p.is_overriding()).map(|p| { |
| 565 | let name = &p.name; |
| 566 | let stripped_name = strip_raw_prefix_from_name(name); |
| 567 | let ident = name_to_ident(name); |
| 568 | let ty = &p.ty; |
| 569 | |
| 570 | let getter = p.get.is_some().then(|| { |
| 571 | let span = p.attrs_span; |
| 572 | parse_quote_spanned!(span=> |
| 573 | #[must_use] |
| 574 | #[allow(dead_code)] |
| 575 | pub fn #ident(&self) -> <#ty as #crate_ident::property::Property>::Value { |
| 576 | self.property::<<#ty as #crate_ident::property::Property>::Value>(#stripped_name) |
| 577 | } |
| 578 | ) |
| 579 | }); |
| 580 | |
| 581 | let setter = (p.set.is_some() && !p.is_construct_only).then(|| { |
| 582 | let ident = format_ident!("set_ {}" , ident); |
| 583 | let target_ty = quote!(<<#ty as #crate_ident::property::Property>::Value as #crate_ident::prelude::HasParamSpec>::SetValue); |
| 584 | let set_ty = if p.nullable { |
| 585 | quote!(::core::option::Option<impl std::borrow::Borrow<#target_ty>>) |
| 586 | } else { |
| 587 | quote!(impl std::borrow::Borrow<#target_ty>) |
| 588 | }; |
| 589 | let upcasted_borrowed_value = if p.nullable { |
| 590 | quote!( |
| 591 | value.as_ref().map(|v| std::borrow::Borrow::borrow(v)) |
| 592 | ) |
| 593 | } else { |
| 594 | quote!( |
| 595 | std::borrow::Borrow::borrow(&value) |
| 596 | ) |
| 597 | }; |
| 598 | let span = p.attrs_span; |
| 599 | parse_quote_spanned!(span=> |
| 600 | #[allow(dead_code)] |
| 601 | pub fn #ident<'a>(&self, value: #set_ty) { |
| 602 | self.set_property_from_value(#stripped_name, &::std::convert::From::from(#upcasted_borrowed_value)) |
| 603 | } |
| 604 | ) |
| 605 | }); |
| 606 | [getter, setter] |
| 607 | }); |
| 608 | defs.flatten() // flattens [] |
| 609 | .flatten() // removes None |
| 610 | .collect::<Vec<_>>() |
| 611 | } |
| 612 | |
| 613 | fn expand_impl_connect_prop_notify(props: &[PropDesc]) -> Vec<syn::ImplItemFn> { |
| 614 | let crate_ident: TokenStream = crate_ident_new(); |
| 615 | let connection_fns: impl Iterator = props.iter().filter(|p: &&PropDesc| !p.is_overriding()).map(|p: &PropDesc| -> syn::ImplItemFn { |
| 616 | let name: &LitStr = &p.name; |
| 617 | let stripped_name: LitStr = strip_raw_prefix_from_name(name); |
| 618 | let fn_ident: Ident = format_ident!("connect_ {}_notify" , name_to_ident(name)); |
| 619 | let span: Span = p.attrs_span; |
| 620 | parse_quote_spanned!(span=> |
| 621 | #[allow(dead_code)] |
| 622 | pub fn #fn_ident<F: Fn(&Self) + 'static>(&self, f: F) -> #crate_ident::SignalHandlerId { |
| 623 | self.connect_notify_local(::core::option::Option::Some(#stripped_name), move |this, _| { |
| 624 | f(this) |
| 625 | }) |
| 626 | } |
| 627 | ) |
| 628 | }); |
| 629 | connection_fns.collect::<Vec<_>>() |
| 630 | } |
| 631 | |
| 632 | fn expand_impl_notify_prop(wrapper_type: &syn::Path, props: &[PropDesc]) -> Vec<syn::ImplItemFn> { |
| 633 | let crate_ident: TokenStream = crate_ident_new(); |
| 634 | let emit_fns: impl Iterator = props.iter().filter(|p: &&PropDesc| !p.is_overriding()).map(|p: &PropDesc| -> syn::ImplItemFn { |
| 635 | let name: LitStr = strip_raw_prefix_from_name(&p.name); |
| 636 | let fn_ident: Ident = format_ident!("notify_ {}" , name_to_ident(&name)); |
| 637 | let span: Span = p.attrs_span; |
| 638 | let enum_ident: Ident = name_to_enum_ident(name.value()); |
| 639 | parse_quote_spanned!(span=> |
| 640 | #[allow(dead_code)] |
| 641 | pub fn #fn_ident(&self) { |
| 642 | self.notify_by_pspec( |
| 643 | &<<#wrapper_type as #crate_ident::object::ObjectSubclassIs>::Subclass |
| 644 | as #crate_ident::subclass::object::DerivedObjectProperties>::derived_properties() |
| 645 | [DerivedPropertiesEnum::#enum_ident as usize] |
| 646 | ); |
| 647 | } |
| 648 | ) |
| 649 | }); |
| 650 | emit_fns.collect::<Vec<_>>() |
| 651 | } |
| 652 | |
| 653 | fn name_to_enum_ident(name: String) -> syn::Ident { |
| 654 | let mut name: String = name.strip_prefix("r#" ).unwrap_or(&name).to_owned(); |
| 655 | let mut slice: &mut str = name.as_mut_str(); |
| 656 | while let Some(i: usize) = slice.find('-' ) { |
| 657 | let (head: &mut str, tail: &mut str) = slice.split_at_mut(mid:i); |
| 658 | if let Some(c: &mut str) = head.get_mut(0..1) { |
| 659 | c.make_ascii_uppercase(); |
| 660 | } |
| 661 | slice = &mut tail[1..]; |
| 662 | } |
| 663 | if let Some(c: &mut str) = slice.get_mut(0..1) { |
| 664 | c.make_ascii_uppercase(); |
| 665 | } |
| 666 | let enum_member: String = name.split('-' ).collect(); |
| 667 | format_ident!(" {}" , enum_member) |
| 668 | } |
| 669 | |
| 670 | fn expand_properties_enum(props: &[PropDesc]) -> TokenStream2 { |
| 671 | if props.is_empty() { |
| 672 | quote! { |
| 673 | #[derive(Debug, Copy, Clone)] |
| 674 | enum DerivedPropertiesEnum {} |
| 675 | impl std::convert::TryFrom<usize> for DerivedPropertiesEnum { |
| 676 | type Error = usize; |
| 677 | |
| 678 | fn try_from(item: usize) -> ::core::result::Result<Self, <Self as std::convert::TryFrom<usize>>::Error> { |
| 679 | ::core::result::Result::Err(item) |
| 680 | } |
| 681 | } |
| 682 | } |
| 683 | } else { |
| 684 | let properties: Vec<syn::Ident> = props |
| 685 | .iter() |
| 686 | .map(|p| { |
| 687 | let name: String = p.name.value(); |
| 688 | |
| 689 | name_to_enum_ident(name) |
| 690 | }) |
| 691 | .collect(); |
| 692 | let props = properties.iter(); |
| 693 | let indices = 0..properties.len(); |
| 694 | quote! { |
| 695 | #[repr(usize)] |
| 696 | #[derive(Debug, Copy, Clone)] |
| 697 | enum DerivedPropertiesEnum { |
| 698 | #(#props,)* |
| 699 | } |
| 700 | impl std::convert::TryFrom<usize> for DerivedPropertiesEnum { |
| 701 | type Error = usize; |
| 702 | |
| 703 | fn try_from(item: usize) -> ::core::result::Result<Self, <Self as std::convert::TryFrom<usize>>::Error> { |
| 704 | match item { |
| 705 | #(#indices => ::core::result::Result::Ok(Self::#properties),)* |
| 706 | _ => ::core::result::Result::Err(item) |
| 707 | } |
| 708 | } |
| 709 | } |
| 710 | } |
| 711 | } |
| 712 | } |
| 713 | |
| 714 | pub fn impl_derive_props(input: PropsMacroInput) -> TokenStream { |
| 715 | let struct_ident = &input.ident; |
| 716 | let crate_ident = crate_ident_new(); |
| 717 | let wrapper_type = input.wrapper_ty; |
| 718 | let fn_properties = expand_properties_fn(&input.props); |
| 719 | let fn_property = expand_property_fn(&input.props); |
| 720 | let fn_set_property = expand_set_property_fn(&input.props); |
| 721 | let getset_properties = expand_impl_getset_properties(&input.props); |
| 722 | let connect_prop_notify = expand_impl_connect_prop_notify(&input.props); |
| 723 | let notify_prop = expand_impl_notify_prop(&wrapper_type, &input.props); |
| 724 | let properties_enum = expand_properties_enum(&input.props); |
| 725 | |
| 726 | let rust_interface = if let Some(ext_trait) = input.ext_trait { |
| 727 | let trait_ident = if let Some(ext_trait) = ext_trait { |
| 728 | ext_trait |
| 729 | } else { |
| 730 | format_ident!( |
| 731 | " {}PropertiesExt" , |
| 732 | wrapper_type.segments.last().unwrap().ident |
| 733 | ) |
| 734 | }; |
| 735 | let fns_without_visibility_modifier = getset_properties |
| 736 | .into_iter() |
| 737 | .chain(connect_prop_notify) |
| 738 | .chain(notify_prop) |
| 739 | .map(|mut item| { |
| 740 | item.vis = syn::Visibility::Inherited; |
| 741 | item |
| 742 | }); |
| 743 | quote! { |
| 744 | pub trait #trait_ident: #crate_ident::prelude::IsA<#wrapper_type> { |
| 745 | #(#fns_without_visibility_modifier)* |
| 746 | } |
| 747 | impl<T: #crate_ident::prelude::IsA<#wrapper_type>> #trait_ident for T {} |
| 748 | } |
| 749 | } else { |
| 750 | quote! { |
| 751 | #[allow(dead_code)] |
| 752 | impl #wrapper_type { |
| 753 | #(#getset_properties)* |
| 754 | #(#connect_prop_notify)* |
| 755 | #(#notify_prop)* |
| 756 | } |
| 757 | } |
| 758 | }; |
| 759 | |
| 760 | let expanded = quote! { |
| 761 | #properties_enum |
| 762 | |
| 763 | impl #crate_ident::subclass::object::DerivedObjectProperties for #struct_ident { |
| 764 | #fn_properties |
| 765 | #fn_property |
| 766 | #fn_set_property |
| 767 | } |
| 768 | |
| 769 | #rust_interface |
| 770 | }; |
| 771 | proc_macro::TokenStream::from(expanded) |
| 772 | } |
| 773 | |