Skip to content

Commit a6e97cc

Browse files
authored
Merge pull request #1081 from rmnscnce/glib-macros-strip-raw-ident-lit
glib-macros: Strip out r# prefix from property names inside the GObject
2 parents 677974a + a392143 commit a6e97cc

File tree

3 files changed

+151
-11
lines changed

3 files changed

+151
-11
lines changed

glib-macros/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,10 @@ pub fn cstr_bytes(item: TokenStream) -> TokenStream {
867867
/// | `<optional-pspec-builder-fields> = expr` | Used to add optional Param Spec builder fields | | `#[property(minimum = 0)` , `#[property(minimum = 0, maximum = 1)]`, etc. |
868868
/// | `<optional-pspec-builder-fields>` | Used to add optional Param Spec builder fields | | `#[property(explicit_notify)]` , `#[property(construct_only)]`, etc. |
869869
///
870+
/// ## Using Rust keywords as property names
871+
/// You might hit a roadblock when declaring properties with this macro because you want to use a name that happens to be a Rust keyword. This may happen with names like `loop`, which is a pretty common name when creating things like animation handlers.
872+
/// To use those names, you can make use of the raw identifier feature of Rust. Simply prefix the identifier name with `r#` in the struct declaration. Internally, those `r#`s are stripped so you can use its expected name in [`ObjectExt::property`] or within GtkBuilder template files.
873+
///
870874
/// # Generated wrapper methods
871875
/// The following methods are generated on the wrapper type specified on `#[properties(wrapper_type = ...)]`:
872876
/// * `$property()`, when the property is readable

glib-macros/src/properties.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use syn::parenthesized;
1111
use syn::parse::Parse;
1212
use syn::punctuated::Punctuated;
1313
use syn::spanned::Spanned;
14+
use syn::LitStr;
1415
use syn::Token;
1516

1617
pub struct PropsMacroInput {
@@ -303,11 +304,14 @@ fn expand_param_spec(prop: &PropDesc) -> TokenStream2 {
303304
let PropDesc {
304305
ty, name, builder, ..
305306
} = prop;
307+
let stripped_name = strip_raw_prefix_from_name(name);
306308

307309
match (&prop.override_class, &prop.override_interface) {
308-
(Some(c), None) => return quote!(#crate_ident::ParamSpecOverride::for_class::<#c>(#name)),
310+
(Some(c), None) => {
311+
return quote!(#crate_ident::ParamSpecOverride::for_class::<#c>(#stripped_name))
312+
}
309313
(None, Some(i)) => {
310-
return quote!(#crate_ident::ParamSpecOverride::for_interface::<#i>(#name))
314+
return quote!(#crate_ident::ParamSpecOverride::for_interface::<#i>(#stripped_name))
311315
}
312316
(Some(_), Some(_)) => {
313317
unreachable!("Both `override_class` and `override_interface` specified")
@@ -328,14 +332,14 @@ fn expand_param_spec(prop: &PropDesc) -> TokenStream2 {
328332
.map(|(mut required_params, chained_methods)| {
329333
let name_expr = syn::ExprLit {
330334
attrs: vec![],
331-
lit: syn::Lit::Str(name.to_owned()),
335+
lit: syn::Lit::Str(stripped_name.to_owned()),
332336
};
333337
required_params.insert(0, name_expr.into());
334338
let required_params = required_params.iter();
335339

336340
quote!((#(#required_params,)*)#chained_methods)
337341
})
338-
.unwrap_or(quote!((#name)));
342+
.unwrap_or(quote!((#stripped_name)));
339343

340344
let builder_fields = prop.builder_fields.iter().map(|(k, v)| quote!(.#k(#v)));
341345

@@ -428,14 +432,14 @@ fn expand_set_property_fn(props: &[PropDesc]) -> TokenStream2 {
428432
ty,
429433
..
430434
} = p;
431-
435+
let stripped_name = strip_raw_prefix_from_name(name);
432436
let crate_ident = crate_ident_new();
433437
let enum_ident = name_to_enum_ident(name.value());
434438
let span = p.attrs_span;
435439
let expect = quote!(.unwrap_or_else(
436440
|err| panic!(
437441
"Invalid conversion from `glib::value::Value` to `{}` inside setter for property `{}`: {:?}",
438-
::std::any::type_name::<<#ty as #crate_ident::Property>::Value>(), #name, err
442+
::std::any::type_name::<<#ty as #crate_ident::Property>::Value>(), #stripped_name, err
439443
)
440444
));
441445
set.as_ref().map(|set| {
@@ -509,16 +513,25 @@ fn name_to_ident(name: &syn::LitStr) -> syn::Ident {
509513
format_ident!("{}", name.value().replace('-', "_"))
510514
}
511515

516+
/// Strips out raw identifier prefix (`r#`) from literal string items
517+
fn strip_raw_prefix_from_name(name: &LitStr) -> LitStr {
518+
LitStr::new(
519+
name.value().strip_prefix("r#").unwrap_or(&name.value()),
520+
name.span(),
521+
)
522+
}
523+
512524
fn expand_wrapper_getset_properties(props: &[PropDesc]) -> TokenStream2 {
513525
let crate_ident = crate_ident_new();
514526
let defs = props.iter().map(|p| {
515527
let name = &p.name;
528+
let stripped_name = strip_raw_prefix_from_name(name);
516529
let ident = name_to_ident(name);
517530
let ty = &p.ty;
518531

519532
let getter = p.get.is_some().then(|| {
520533
quote!(pub fn #ident(&self) -> <#ty as #crate_ident::Property>::Value {
521-
self.property::<<#ty as #crate_ident::Property>::Value>(#name)
534+
self.property::<<#ty as #crate_ident::Property>::Value>(#stripped_name)
522535
})
523536
});
524537
let is_construct_only = p.builder_fields.iter().any(|(k, _)| *k == "construct_only");
@@ -540,7 +553,7 @@ fn expand_wrapper_getset_properties(props: &[PropDesc]) -> TokenStream2 {
540553
)
541554
};
542555
quote!(pub fn #ident<'a>(&self, value: #set_ty) {
543-
self.set_property_from_value(#name, &::std::convert::From::from(#upcasted_borrowed_value))
556+
self.set_property_from_value(#stripped_name, &::std::convert::From::from(#upcasted_borrowed_value))
544557
})
545558
});
546559
let span = p.attrs_span;
@@ -556,10 +569,11 @@ fn expand_wrapper_connect_prop_notify(props: &[PropDesc]) -> TokenStream2 {
556569
let crate_ident = crate_ident_new();
557570
let connection_fns = props.iter().map(|p| {
558571
let name = &p.name;
572+
let stripped_name = strip_raw_prefix_from_name(name);
559573
let fn_ident = format_ident!("connect_{}_notify", name_to_ident(name));
560574
let span = p.attrs_span;
561575
quote_spanned!(span=> pub fn #fn_ident<F: Fn(&Self) + 'static>(&self, f: F) -> #crate_ident::SignalHandlerId {
562-
self.connect_notify_local(::core::option::Option::Some(#name), move |this, _| {
576+
self.connect_notify_local(::core::option::Option::Some(#stripped_name), move |this, _| {
563577
f(this)
564578
})
565579
})
@@ -570,8 +584,8 @@ fn expand_wrapper_connect_prop_notify(props: &[PropDesc]) -> TokenStream2 {
570584
fn expand_wrapper_notify_prop(props: &[PropDesc]) -> TokenStream2 {
571585
let crate_ident = crate_ident_new();
572586
let emit_fns = props.iter().map(|p| {
573-
let name = &p.name;
574-
let fn_ident = format_ident!("notify_{}", name_to_ident(name));
587+
let name = strip_raw_prefix_from_name(&p.name);
588+
let fn_ident = format_ident!("notify_{}", name_to_ident(&name));
575589
let span = p.attrs_span;
576590
let enum_ident = name_to_enum_ident(name.value());
577591
quote_spanned!(span=> pub fn #fn_ident(&self) {

glib-macros/tests/properties.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,125 @@ fn props() {
384384
myfoo.set_object(Some(myobj.upcast_ref()));
385385
assert_eq!(myfoo.object(), Some(myobj.upcast()))
386386
}
387+
388+
#[cfg(test)]
389+
mod kw_names {
390+
mod imp {
391+
use glib::subclass::types::ObjectSubclassExt;
392+
use glib::subclass::{object::DerivedObjectProperties, prelude::ObjectImplExt};
393+
use glib::ObjectExt;
394+
use std::cell::Cell;
395+
396+
use glib::{
397+
subclass::{prelude::ObjectImpl, types::ObjectSubclass},
398+
ParamSpec, Value,
399+
};
400+
use glib_macros::Properties;
401+
402+
#[derive(Properties, Default)]
403+
#[properties(wrapper_type = super::KwNames)]
404+
pub struct KwNames {
405+
// Some of the strict keywords
406+
#[property(get, set)]
407+
r#loop: Cell<u8>,
408+
#[property(get, set)]
409+
r#move: Cell<u8>,
410+
#[property(get, set)]
411+
r#type: Cell<u8>,
412+
413+
// Lexer 2018+ strict keywords
414+
#[property(get, set)]
415+
r#async: Cell<u8>,
416+
#[property(get, set)]
417+
r#await: Cell<u8>,
418+
#[property(get, set)]
419+
r#dyn: Cell<u8>,
420+
421+
// Some of the reserved keywords
422+
#[property(get, set)]
423+
r#become: Cell<u8>,
424+
#[property(get, set)]
425+
r#macro: Cell<u8>,
426+
#[property(get, set)]
427+
r#unsized: Cell<u8>,
428+
429+
// Lexer 2018+ reserved keywords
430+
#[property(get, set)]
431+
r#try: Cell<u8>,
432+
}
433+
434+
#[glib::object_subclass]
435+
impl ObjectSubclass for KwNames {
436+
const NAME: &'static str = "MyKwNames";
437+
type Type = super::KwNames;
438+
}
439+
440+
impl ObjectImpl for KwNames {
441+
fn properties() -> &'static [ParamSpec] {
442+
Self::derived_properties()
443+
}
444+
fn set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) {
445+
Self::derived_set_property(self, _id, _value, _pspec)
446+
}
447+
fn property(&self, id: usize, _pspec: &ParamSpec) -> Value {
448+
Self::derived_property(self, id, _pspec)
449+
}
450+
}
451+
}
452+
453+
glib::wrapper! {
454+
pub struct KwNames(ObjectSubclass<imp::KwNames>);
455+
}
456+
}
457+
458+
#[test]
459+
fn keyword_propnames() {
460+
let mykwnames: kw_names::KwNames = glib::Object::new();
461+
462+
// make sure all 10 properties are registered
463+
assert_eq!(mykwnames.list_properties().len(), 10);
464+
465+
// getting property values
466+
assert_eq!(mykwnames.r#loop(), 0);
467+
assert_eq!(mykwnames.r#move(), 0);
468+
assert_eq!(mykwnames.r#type(), 0);
469+
assert_eq!(mykwnames.r#async(), 0);
470+
assert_eq!(mykwnames.r#await(), 0);
471+
assert_eq!(mykwnames.r#try(), 0);
472+
473+
// getting property by name
474+
assert_eq!(mykwnames.property::<u8>("loop"), 0);
475+
assert_eq!(mykwnames.property::<u8>("move"), 0);
476+
assert_eq!(mykwnames.property::<u8>("type"), 0);
477+
assert_eq!(mykwnames.property::<u8>("async"), 0);
478+
assert_eq!(mykwnames.property::<u8>("await"), 0);
479+
assert_eq!(mykwnames.property::<u8>("try"), 0);
480+
481+
// setting property values
482+
mykwnames.set_loop(128_u8);
483+
assert_eq!(mykwnames.r#loop(), 128_u8);
484+
mykwnames.set_move(128_u8);
485+
assert_eq!(mykwnames.r#move(), 128_u8);
486+
mykwnames.set_type(128_u8);
487+
assert_eq!(mykwnames.r#type(), 128_u8);
488+
mykwnames.set_async(128_u8);
489+
assert_eq!(mykwnames.r#async(), 128_u8);
490+
mykwnames.set_await(128_u8);
491+
assert_eq!(mykwnames.r#await(), 128_u8);
492+
mykwnames.set_try(128_u8);
493+
assert_eq!(mykwnames.r#try(), 128_u8);
494+
495+
// setting property by name
496+
mykwnames.set_property("loop", 255_u8);
497+
assert_eq!(mykwnames.r#loop(), 255_u8);
498+
mykwnames.set_property("move", 255_u8);
499+
assert_eq!(mykwnames.r#loop(), 255_u8);
500+
mykwnames.set_property("type", 255_u8);
501+
assert_eq!(mykwnames.r#loop(), 255_u8);
502+
mykwnames.set_property("async", 255_u8);
503+
assert_eq!(mykwnames.r#async(), 255_u8);
504+
mykwnames.set_property("await", 255_u8);
505+
assert_eq!(mykwnames.r#await(), 255_u8);
506+
mykwnames.set_property("try", 255_u8);
507+
assert_eq!(mykwnames.r#try(), 255_u8);
508+
}

0 commit comments

Comments
 (0)