Skip to content

Commit 43c6b47

Browse files
committed
Use an actual type for the gui(Event = ...) attribute
The current implementation of gui-derive's Event attribute has the problem that the user specifies the event type to use in representation of a string. That is a problem for many reasons, two of which are: - when an event is used only in an attribute (by virtue of being a string there), the compiler will flag it as unused - type lookup (using the RLS) will simply not work as expected because a string looses all the type information It was not an accident that this functionality was implemented this way as the unrestricted_attribute_tokens feature, which allows for non-string tokens to be used as attributes, was unstable at the time this functionality was implemented, causing compilation errors such as the following: > error: expected unsuffixed literal or identifier, found Event2 > --> tests/test_derive.rs:38:7 > | > 38 | #[gui(Event = Event2)] > | ^^^^^ > | > = help: try enabling `#![feature(unrestricted_attribute_tokens)]` Now that [Rust issue 55208][issue-55208] has landed and reached the stable toolchain, this change implements the functionality properly. [issue-55208]: rust-lang/rust#55208
1 parent b515d6f commit 43c6b47

File tree

6 files changed

+80
-59
lines changed

6 files changed

+80
-59
lines changed

derive/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
Unreleased
22
----------
3+
- Made `Event = ...` attribute support actual event type and not just
4+
string representation of it
35
- Bumped minimum required Rust version to `1.34.0`
46

57

derive/src/lib.rs

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,19 @@ use proc_macro2::Span;
5050
use quote::__rt::TokenStream as Tokens;
5151
use quote::quote;
5252
use syn::Attribute;
53+
use syn::Binding;
5354
use syn::Data;
5455
use syn::DeriveInput;
5556
use syn::Fields;
5657
use syn::GenericParam;
5758
use syn::Generics;
58-
use syn::Lit;
59-
use syn::Meta;
60-
use syn::NestedMeta;
59+
use syn::parenthesized;
60+
use syn::parse::Parse;
61+
use syn::parse::ParseStream;
6162
use syn::parse2;
6263
use syn::punctuated::Punctuated;
6364
use syn::token::Comma;
65+
use syn::Type;
6466
use syn::TypeGenerics;
6567
use syn::WhereClause;
6668
use syn::WherePredicate;
@@ -69,7 +71,7 @@ use syn::WherePredicate;
6971
/// A type indicating whether or not to create a default implementation of Type::new().
7072
type New = Option<()>;
7173
/// A type representing an event type to parametrize a widget with.
72-
type Event = Option<String>;
74+
type Event = Option<Type>;
7375

7476

7577
/// The error type used internally by this module.
@@ -185,60 +187,79 @@ fn parse_attributes(attributes: &[Attribute]) -> Result<(New, Event)> {
185187
}
186188

187189
/// Parse a single item in a #[gui(list...)] attribute list.
188-
fn parse_gui_attribute(item: &NestedMeta) -> Result<(New, Event)> {
189-
match *item {
190-
NestedMeta::Meta(ref meta_item) => {
191-
match *meta_item {
192-
Meta::NameValue(ref name_val) if name_val.ident == "Event" => {
193-
match name_val.lit {
194-
Lit::Str(ref string) => Ok((None, Some(string.value()))),
195-
_ => Ok((None, None)),
196-
}
197-
},
198-
Meta::Word(ref ident) if ident == "default_new" => Ok((Some(()), None)),
199-
_ => Err(Error::from(format!("unsupported attribute: {}", meta_item.name()))),
190+
fn parse_gui_attribute(item: Attr) -> Result<(New, Event)> {
191+
match item {
192+
Attr::Ident(ref ident) if ident == "default_new" => {
193+
Ok((Some(()), None))
194+
},
195+
Attr::Binding(binding) => {
196+
// Unfortunately we can't use a pattern guard here. See issue
197+
// https://github.com/rust-lang/rust/issues/15287 for more
198+
// details.
199+
if binding.ident == "Event" {
200+
Ok((None, Some(binding.ty)))
201+
} else {
202+
Err(Error::from("encountered unknown attribute"))
200203
}
201204
},
202-
NestedMeta::Literal(_) => Err(Error::from("unsupported literal")),
205+
_ => Err(Error::from("encountered unknown attribute")),
203206
}
204207
}
205208

206209
/// Parse a #[gui(list...)] attribute list.
207-
fn parse_gui_attributes(list: &Punctuated<NestedMeta, Comma>) -> Result<(New, Event)> {
210+
fn parse_gui_attributes(list: AttrList) -> Result<(New, Event)> {
208211
let mut new = None;
209212
let mut event = None;
210213

211-
for item in list {
214+
for item in list.0 {
212215
let (this_new, this_event) = parse_gui_attribute(item)?;
213216
new = this_new.or(new);
214217
event = this_event.or(event);
215218
}
216219
Ok((new, event))
217220
}
218221

219-
/// Parse a single attribute, e.g., #[GuiType = "Widget"].
220-
// TODO: Once https://github.com/rust-lang/rust/pull/57367 lands in
221-
// stable we should migrate to using the actual type and not a
222-
// textual representation of it.
223-
fn parse_attribute(attribute: &Attribute) -> Result<(New, Event)> {
224-
// We don't care about the other meta data elements, inner/outer,
225-
// doc/non-doc, it's all fine by us.
226-
227-
match attribute.interpret_meta() {
228-
Some(x) => {
229-
match x {
230-
Meta::List(ref list) if list.ident == "gui" => {
231-
// Here we have found an attribute of the form #[gui(xxx, yyy,
232-
// ...)]. Parse the inner list.
233-
parse_gui_attributes(&list.nested)
234-
},
235-
_ => Ok((None, None)),
236-
}
237-
},
238-
None => Ok((None, None)),
222+
223+
/// An attribute list representing an syn::Attribute::tts.
224+
struct AttrList(Punctuated<Attr, Comma>);
225+
226+
impl Parse for AttrList {
227+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
228+
let content;
229+
let _ = parenthesized!(content in input);
230+
let list = content.parse_terminated(Attr::parse)?;
231+
232+
Ok(Self(list))
239233
}
240234
}
241235

236+
237+
enum Attr {
238+
Ident(Ident),
239+
Binding(Binding),
240+
}
241+
242+
impl Parse for Attr {
243+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
244+
if let Ok(bind) = input.parse::<Binding>() {
245+
Ok(Attr::Binding(bind))
246+
} else {
247+
input.parse::<Ident>().map(|ident| Attr::Ident(ident))
248+
}
249+
}
250+
}
251+
252+
253+
/// Parse a single attribute, e.g., #[Event = MyEvent].
254+
fn parse_attribute(attribute: &Attribute) -> Result<(New, Event)> {
255+
let tokens = attribute.tts.clone();
256+
let attr = parse2::<AttrList>(tokens).or_else(|err| {
257+
Err(format!("unable to parse attributes: {:?}", err))
258+
})?;
259+
260+
parse_gui_attributes(attr)
261+
}
262+
242263
/// Expand the input with the implementation of the required traits.
243264
fn expand_widget_input(new: New, event: &Event, input: &DeriveInput) -> Result<Tokens> {
244265
match input.data {
@@ -353,12 +374,12 @@ fn expand_widget_trait(event: &Event, input: &DeriveInput) -> Tokens {
353374
let generic = event.is_none();
354375
let (generics, ty_generics, where_clause) = split_for_impl(&input.generics, generic);
355376

356-
let event = if let Some(event) = event {
357-
Ident::new(&event, Span::call_site())
377+
let widget = if let Some(event) = event {
378+
quote! { ::gui::Widget<#event> }
358379
} else {
359-
Ident::new("__E", Span::call_site())
380+
let event = Ident::new("__E", Span::call_site());
381+
quote! { ::gui::Widget<#event> }
360382
};
361-
let widget = quote! { ::gui::Widget<#event> };
362383

363384
quote! {
364385
impl #generics #widget for #name #ty_generics #where_clause {
@@ -470,12 +491,12 @@ fn expand_handleable_trait(event: &Event, input: &DeriveInput) -> Tokens {
470491
let generic = event.is_none();
471492
let (generics, ty_generics, where_clause) = split_for_impl(&input.generics, generic);
472493

473-
let event = if let Some(event) = event {
474-
Ident::new(&event, Span::call_site())
494+
let handleable = if let Some(event) = event {
495+
quote! { ::gui::Handleable<#event> }
475496
} else {
476-
Ident::new("__E", Span::call_site())
497+
let event = Ident::new("__E", Span::call_site());
498+
quote! { ::gui::Handleable<#event> }
477499
};
478-
let handleable = quote! { ::gui::Handleable<#event> };
479500

480501
quote! {
481502
impl #generics #handleable for #name #ty_generics #where_clause {}

derive/tests/test_derive.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,11 @@ use gui::UnhandledEvent;
4343
use gui::Widget;
4444

4545

46-
#[allow(unused)]
4746
type Event = ();
4847

4948

5049
#[derive(Debug, Widget, Handleable)]
51-
#[gui(default_new, Event = "Event")]
50+
#[gui(Event = ())]
5251
struct TestWidget {
5352
id: Id,
5453
}
@@ -58,7 +57,7 @@ struct TestWidget {
5857
// purposes.
5958
#[deny(unused_imports)]
6059
#[derive(Debug, Widget)]
61-
#[gui(default_new, Event = "Event")]
60+
#[gui(default_new, Event = ())]
6261
struct TestWidgetCustom {
6362
id: Id,
6463
}
@@ -67,7 +66,7 @@ impl Handleable<Event> for TestWidgetCustom {}
6766

6867

6968
#[derive(Debug, Widget, Handleable)]
70-
#[gui(Event = "Event")]
69+
#[gui(Event = Event)]
7170
struct TestWidgetT<T>
7271
where
7372
T: 'static + Debug,
@@ -90,7 +89,7 @@ where
9089

9190

9291
#[derive(Debug, Handleable)]
93-
#[gui(Event = "Event")]
92+
#[gui(Event = Event)]
9493
struct TestHandleable {
9594
id: Id,
9695
}
@@ -158,7 +157,7 @@ impl MyEvent for CustomEvent {
158157

159158

160159
#[derive(Debug, Widget)]
161-
#[gui(Event = "E")]
160+
#[gui(Event = E)]
162161
struct TestGenericEvent<E>
163162
where
164163
E: Debug + MyEvent + 'static,

src/renderer.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,13 @@ pub trait Renderer {
6161
/// ```rust
6262
/// # use gui::{BBox, Cap, Id, Renderer, Renderable};
6363
/// # use gui::derive::{Handleable, Widget};
64-
/// # type Event = ();
6564
/// # #[derive(Debug, Widget, Handleable)]
66-
/// # #[gui(Event = "Event")]
65+
/// # #[gui(Event = ())]
6766
/// # struct ConcreteWidget1 {
6867
/// # id: Id,
6968
/// # }
7069
/// # #[derive(Debug, Widget, Handleable)]
71-
/// # #[gui(Event = "Event")]
70+
/// # #[gui(Event = ())]
7271
/// # struct ConcreteWidget2 {
7372
/// # id: Id,
7473
/// # }

tests/common/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ impl TestWidgetBuilder {
132132

133133

134134
#[derive(Debug, Widget)]
135-
#[gui(Event = "Event")]
135+
#[gui(Event = Event)]
136136
pub struct TestWidget {
137137
id: Id,
138138
event_handler: Option<EventHandler>,

tests/test_ui.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ fn need_more(id: Id, cap: &mut dyn MutCap<Event>) -> bool {
422422
}
423423

424424
#[derive(Debug, Widget)]
425-
#[gui(Event = "Event")]
425+
#[gui(Event = Event)]
426426
struct CreatingWidget {
427427
id: Id,
428428
}
@@ -481,7 +481,7 @@ fn recursive_widget_creation() {
481481
struct Moveable {}
482482

483483
#[derive(Debug, Widget, Handleable)]
484-
#[gui(Event = "Event")]
484+
#[gui(Event = Event)]
485485
struct MovingWidget {
486486
id: Id,
487487
object: Moveable,

0 commit comments

Comments
 (0)