gui_derive/
lib.rs

1// Copyright (C) 2018-2025 Daniel Mueller <deso@posteo.net>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! A crate providing custom derive functionality for the `gui` crate.
5
6use std::fmt::Display;
7use std::fmt::Formatter;
8use std::fmt::Result as FmtResult;
9use std::result;
10
11use proc_macro::LexError;
12use proc_macro::TokenStream;
13use proc_macro2::Ident;
14use proc_macro2::Span;
15use proc_macro2::TokenStream as Tokens;
16use quote::quote;
17use syn::Attribute;
18use syn::Binding;
19use syn::Data;
20use syn::DeriveInput;
21use syn::Fields;
22use syn::GenericParam;
23use syn::Generics;
24use syn::parenthesized;
25use syn::parse::Parse;
26use syn::parse::ParseStream;
27use syn::parse2;
28use syn::punctuated::Punctuated;
29use syn::token::Comma;
30use syn::token::Eq;
31use syn::Type;
32use syn::TypeGenerics;
33use syn::WhereClause;
34use syn::WherePredicate;
35
36
37/// A type indicating whether or not to create a default implementation of `Type::new()`.
38type New = Option<()>;
39/// An event type to parametrize a widget with.
40type Event = Option<Type>;
41/// A message type to parametrize a widget with.
42type Message = Option<Type>;
43
44
45/// The error type used internally by this module.
46#[derive(Debug)]
47enum Error {
48  Error(String),
49  LexError(LexError),
50}
51
52impl Display for Error {
53  fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
54    match *self {
55      Error::Error(ref e) => write!(f, "{e}"),
56      Error::LexError(ref e) => write!(f, "{e:?}"),
57    }
58  }
59}
60
61impl From<String> for Error {
62  fn from(string: String) -> Error {
63    Error::Error(string)
64  }
65}
66
67impl From<&'static str> for Error {
68  fn from(string: &'static str) -> Error {
69    Error::Error(string.to_string())
70  }
71}
72
73impl From<LexError> for Error {
74  fn from(error: LexError) -> Error {
75    Error::LexError(error)
76  }
77}
78
79type Result<T> = result::Result<T, Error>;
80
81
82/// Custom derive functionality for the `gui::Widget` trait.
83///
84/// Using this macro a default implementation of the `gui::Widget`
85/// trait can be created. Note that this trait is just a unification of
86/// the `gui::Object`, `gui::Renderable`, and `gui::Handleable` traits.
87/// Note furthermore that only implementations of the former two will be
88/// auto generated. The reason for this behavior is that
89/// `gui::Handleable` most likely needs customization to accommodate for
90/// custom event handling behavior.
91///
92/// This macro roughly expands to the following code:
93///
94/// ```rust
95/// # use std::any::TypeId;
96/// # type Event = ();
97/// # type Message = ();
98/// # #[derive(Debug)]
99/// # struct TestWidget {
100/// #   id: gui::Id,
101/// # }
102/// impl gui::Renderable for TestWidget {
103///   fn type_id(&self) -> TypeId {
104///     TypeId::of::<TestWidget>()
105///   }
106///   fn render(
107///     &self,
108///     cap: &dyn gui::Cap,
109///     renderer: &dyn gui::Renderer,
110///     bbox: gui::BBox,
111///   ) -> gui::BBox {
112///     renderer.render(self, cap, bbox)
113///   }
114/// }
115///
116/// impl gui::Object for TestWidget {
117///   fn id(&self) -> gui::Id {
118///     self.id
119///   }
120/// }
121///
122/// impl gui::Widget<Event, Message> for TestWidget {
123///   fn type_id(&self) -> TypeId {
124///     TypeId::of::<TestWidget>()
125///   }
126/// }
127/// # impl gui::Handleable<Event, Message> for TestWidget {}
128/// ```
129#[proc_macro_derive(Widget, attributes(gui))]
130pub fn widget(input: TokenStream) -> TokenStream {
131  match expand_widget(input) {
132    Ok(tokens) => tokens,
133    Err(error) => panic!("{}", error),
134  }
135}
136
137fn expand_widget(input: TokenStream) -> Result<TokenStream> {
138  let input = parse2::<DeriveInput>(input.into()).map_err(|_| "unable to parse input")?;
139  let (new, event, message) = parse_attributes(&input.attrs)?;
140  let tokens = expand_widget_input(new, &event, &message, &input)?;
141  Ok(tokens.into())
142}
143
144/// Parse the macro's attributes.
145fn parse_attributes(attributes: &[Attribute]) -> Result<(New, Event, Message)> {
146  let (new, event, message) = attributes.iter().map(parse_attribute).try_fold(
147    (None, None, None),
148    |(new1, event1, message1), result2| {
149      let (new2, event2, message2) = result2?;
150      Result::Ok((new2.or(new1), event2.or(event1), message2.or(message1)))
151    },
152  )?;
153
154  // If no attribute is given we do not create a default implementation
155  // of new().
156  Ok((new, event, message))
157}
158
159/// Parse a single item in a `#[gui(list...)]` attribute list.
160fn parse_gui_attribute(item: Attr) -> Result<(New, Event, Message)> {
161  match item {
162    Attr::Ident(ref ident) if ident == "default_new" => {
163      Ok((Some(()), None, None))
164    },
165    Attr::Binding(binding) => {
166      if binding.ident == "Event" {
167        Ok((None, Some(binding.ty), None))
168      } else if binding.ident == "Message" {
169        Ok((None, None, Some(binding.ty)))
170      } else {
171        Err(Error::from("encountered unknown binding attribute"))
172      }
173    },
174    _ => Err(Error::from("encountered unknown attribute")),
175  }
176}
177
178/// Parse a #[gui(list...)] attribute list.
179fn parse_gui_attributes(list: AttrList) -> Result<(New, Event, Message)> {
180  let mut new = None;
181  let mut event = None;
182  let mut message = None;
183
184  for item in list.0 {
185    let (this_new, this_event, this_message) = parse_gui_attribute(item)?;
186    new = this_new.or(new);
187    event = this_event.or(event);
188    message = this_message.or(message);
189  }
190  Ok((new, event, message))
191}
192
193
194/// An attribute list representing a `syn::Attribute::tts`.
195struct AttrList(Punctuated<Attr, Comma>);
196
197impl Parse for AttrList {
198  fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
199    let content;
200    let _ = parenthesized!(content in input);
201    let list = content.parse_terminated(Attr::parse)?;
202
203    Ok(Self(list))
204  }
205}
206
207
208#[allow(clippy::large_enum_variant)]
209enum Attr {
210  Ident(Ident),
211  Binding(Binding),
212}
213
214impl Parse for Attr {
215  fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
216    // We need to peek at the second token coming up next first, because
217    // attempting to parse it would advance the position in the buffer.
218    if input.peek2(Eq) {
219      let bind = input.parse::<Binding>()?;
220      Ok(Attr::Binding(bind))
221    } else {
222      input.parse::<Ident>().map(Attr::Ident)
223    }
224  }
225}
226
227
228/// Parse a single attribute, e.g., `#[Event = MyEvent]`.
229fn parse_attribute(attribute: &Attribute) -> Result<(New, Event, Message)> {
230  if attribute.path.is_ident("gui") {
231    let tokens = attribute.tokens.clone();
232    let attr = parse2::<AttrList>(tokens).map_err(|err| {
233      format!("unable to parse attributes: {err:?}")
234    })?;
235
236    parse_gui_attributes(attr)
237  } else {
238    Ok((None, None, None))
239  }
240}
241
242/// Expand the input with the implementation of the required traits.
243fn expand_widget_input(
244  new: New,
245  event: &Event,
246  message: &Message,
247  input: &DeriveInput,
248) -> Result<Tokens> {
249  match input.data {
250    Data::Struct(ref data) => {
251      check_struct_fields(&data.fields)?;
252      Ok(expand_widget_traits(new, event, message, input))
253    },
254    _ => Err(Error::from("#[derive(Widget)] is only defined for structs")),
255  }
256}
257
258/// Check the fields of the user's struct for required attributes.
259// Note that we only check for the names of the required fields, not for
260// the types. Checking types is cumbersome and best-effort anyway as we
261// are working on tokens without context (a user could have a field of
262// type Id but that could map to ::foo::Id and not ::gui::Id).
263fn check_struct_fields(fields: &Fields) -> Result<()> {
264  let checks = [("id", "::gui::Id")];
265
266  for (req_field, req_type) in &checks {
267    let _ = fields
268      .iter()
269      .find(|field| {
270        if let Some(ref ident) = field.ident {
271          ident == req_field
272        } else {
273          false
274        }
275      })
276      .ok_or_else(|| Error::from(format!("struct field {req_field}: {req_type} not found")))?;
277  }
278  Ok(())
279}
280
281/// Expand the struct input with the implementation of the required traits.
282fn expand_widget_traits(new: New, event: &Event, message: &Message, input: &DeriveInput) -> Tokens {
283  let new_impl = expand_new_impl(new, input);
284  let renderable = expand_renderable_trait(input);
285  let object = expand_object_trait(input);
286  let widget = expand_widget_trait(event, message, input);
287
288  quote! {
289    #new_impl
290    #renderable
291    #object
292    #widget
293  }
294}
295
296
297/// Expand an implementation of `Type::new()` for the struct.
298fn expand_new_impl(new: New, input: &DeriveInput) -> Tokens {
299  let name = &input.ident;
300  let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
301
302  match new {
303    Some(..) => {
304      quote! {
305        #[allow(dead_code)]
306        impl #impl_generics #name #ty_generics #where_clause {
307          pub fn new(id: ::gui::Id) -> Self {
308            #name {
309              id,
310            }
311          }
312        }
313      }
314    },
315    None => quote! {},
316  }
317}
318
319/// Expand an implementation for the `gui::Renderable` trait.
320fn expand_renderable_trait(input: &DeriveInput) -> Tokens {
321  let name = &input.ident;
322  let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
323
324  quote! {
325    impl #impl_generics ::gui::Renderable for #name #ty_generics #where_clause {
326      fn type_id(&self) -> ::std::any::TypeId {
327        ::std::any::TypeId::of::<#name #ty_generics>()
328      }
329
330      fn render(
331        &self,
332        cap: &dyn ::gui::Cap,
333        renderer: &dyn ::gui::Renderer,
334        bbox: ::gui::BBox,
335      ) -> ::gui::BBox {
336        renderer.render(self, cap, bbox)
337      }
338
339      fn render_done(
340        &self,
341        cap: &dyn ::gui::Cap,
342        renderer: &dyn ::gui::Renderer,
343        bbox: ::gui::BBox,
344      ) {
345        renderer.render_done(self, cap, bbox)
346      }
347    }
348  }
349}
350
351
352/// Expand an implementation for the `gui::Object` trait.
353fn expand_object_trait(input: &DeriveInput) -> Tokens {
354  let name = &input.ident;
355  let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
356
357  quote! {
358    impl #impl_generics ::gui::Object for #name #ty_generics #where_clause {
359      fn id(&self) -> ::gui::Id {
360        self.id
361      }
362    }
363  }
364}
365
366/// Expand an implementation for the `gui::Widget` trait.
367fn expand_widget_trait(event: &Event, message: &Message, input: &DeriveInput) -> Tokens {
368  let name = &input.ident;
369  let (generics, ty_generics, where_clause) = split_for_impl(&input.generics, event, message);
370
371  let event = if let Some(event) = event {
372    quote! { #event }
373  } else {
374    let ident = Ident::new("__E", Span::call_site());
375    quote! { #ident }
376  };
377
378  let message = if let Some(message) = message {
379    quote! { #message }
380  } else {
381    let ident = Ident::new("__M", Span::call_site());
382    quote! { #ident }
383  };
384
385  let widget = quote! { ::gui::Widget<#event, #message> };
386  quote! {
387    impl #generics #widget for #name #ty_generics #where_clause {
388      fn type_id(&self) -> ::std::any::TypeId {
389        ::std::any::TypeId::of::<#name #ty_generics>()
390      }
391    }
392  }
393}
394
395/// Custom derive functionality for the `gui::Handleable` trait.
396///
397/// Using this macro a default implementation of the `gui::Handleable`
398/// trait can be created. This functionality is mostly used in quick
399/// prototyping/testing scenarios, because most custom widgets will also
400/// need a custom event handler.
401///
402/// This macro roughly expands to the following code:
403///
404/// ```rust
405/// # use gui_derive::Widget;
406/// # type Event = ();
407/// # type Message = ();
408/// # #[derive(Debug, Widget)]
409/// # #[gui(Event = Event, Message = Message)]
410/// # struct TestWidget {
411/// #   id: gui::Id,
412/// # }
413/// impl gui::Handleable<Event, Message> for TestWidget {}
414/// # fn main() {}
415/// ```
416#[proc_macro_derive(Handleable, attributes(gui))]
417pub fn handleable(input: TokenStream) -> TokenStream {
418  match expand_handleable(input) {
419    Ok(tokens) => tokens,
420    Err(error) => panic!("{}", error),
421  }
422}
423
424fn expand_handleable(input: TokenStream) -> Result<TokenStream> {
425  let input = parse2::<DeriveInput>(input.into()).map_err(|_| "unable to parse input")?;
426  let (_, event, message) = parse_attributes(&input.attrs)?;
427  let tokens = expand_handleable_input(&event, &message, &input)?;
428  Ok(tokens.into())
429}
430
431/// Expand the input with the implementation of the required traits.
432fn expand_handleable_input(
433  event: &Event,
434  message: &Message,
435  input: &DeriveInput,
436) -> Result<Tokens> {
437  match input.data {
438    Data::Struct(_) => Ok(expand_handleable_trait(event, message, input)),
439    _ => Err(Error::from("#[derive(Handleable)] is only defined for structs")),
440  }
441}
442
443/// Extend generics with a type parameter named as per the given
444/// identifier.
445fn extend_generics(generics: &Generics, ident: Ident) -> Generics {
446  let param = GenericParam::Type(ident.into());
447  let mut generics = generics.clone();
448  generics.params.push(param);
449  generics
450}
451
452/// Extended a where clause with the provided identifier.
453fn extend_where_clause(where_clause: &Option<WhereClause>, ident: &Ident) -> WhereClause {
454  if let Some(where_clause) = where_clause {
455    let predicate = quote! { #ident: 'static };
456    let predicate = parse2::<WherePredicate>(predicate).unwrap();
457    let mut where_clause = where_clause.clone();
458    where_clause.predicates.push(predicate);
459    where_clause
460  } else {
461    // Strictly speaking we should always have a where clause because
462    // Handleable and Widget have additional trait constraints. However,
463    // if the user forgets we would hit this code path before the
464    // compiler could actually provide a hint (in the form of an error)
465    // that clarifies the mistake. So just provide sane behavior here as
466    // well.
467    let where_clause = quote! { where #ident: 'static };
468    parse2::<WhereClause>(where_clause).unwrap()
469  }
470}
471
472/// Split a type's generics into the pieces required for impl'ing a
473/// trait for that type, while correctly handling potential generic
474/// event and types.
475fn split_for_impl<'g>(
476  generics: &'g Generics,
477  event: &Event,
478  message: &Message,
479) -> (Generics, TypeGenerics<'g>, Option<WhereClause>) {
480  let (_, ty_generics, _) = generics.split_for_impl();
481  let generics = generics.clone();
482  let where_clause = generics.where_clause.clone();
483
484  let (generics, where_clause) = if event.is_none() {
485    let ident = Ident::new("__E", Span::call_site());
486    let generics = extend_generics(&generics, ident.clone());
487    let where_clause = extend_where_clause(&where_clause, &ident);
488    (generics, Some(where_clause))
489  } else {
490    (generics, where_clause)
491  };
492
493  let (generics, where_clause) = if message.is_none() {
494    let ident = Ident::new("__M", Span::call_site());
495    let generics = extend_generics(&generics, ident.clone());
496    let where_clause = extend_where_clause(&where_clause, &ident);
497    (generics, Some(where_clause))
498  } else {
499    (generics, where_clause)
500  };
501
502  (generics, ty_generics, where_clause)
503}
504
505/// Expand an implementation for the `gui::Handleable` trait.
506fn expand_handleable_trait(event: &Event, message: &Message, input: &DeriveInput) -> Tokens {
507  let name = &input.ident;
508  let (generics, ty_generics, where_clause) = split_for_impl(&input.generics, event, message);
509
510  let event = if let Some(event) = event {
511    quote! { #event }
512  } else {
513    let ident = Ident::new("__E", Span::call_site());
514    quote! { #ident }
515  };
516
517  let message = if let Some(message) = message {
518    quote! { #message }
519  } else {
520    let ident = Ident::new("__M", Span::call_site());
521    quote! { #ident }
522  };
523
524  let handleable = quote! { ::gui::Handleable<#event, #message> };
525  quote! {
526    impl #generics #handleable for #name #ty_generics #where_clause {}
527  }
528}
529
530
531#[cfg(test)]
532mod tests {
533  use super::*;
534
535  #[test]
536  fn default_widget_attributes() {
537    let tokens = quote! {
538      struct Bar { }
539    };
540
541    let input = parse2::<DeriveInput>(tokens).unwrap();
542    let (new, event, message) = parse_attributes(&input.attrs).unwrap();
543    assert_eq!(new, None);
544    assert_eq!(event, None);
545    assert_eq!(message, None);
546  }
547
548  #[test]
549  fn default_new() {
550    let tokens = quote! {
551      #[gui(default_new)]
552      struct Bar { }
553    };
554
555    let input = parse2::<DeriveInput>(tokens).unwrap();
556    let (new, event, message) = parse_attributes(&input.attrs).unwrap();
557    assert_eq!(new, Some(()));
558    assert_eq!(event, None);
559    assert_eq!(message, None);
560  }
561
562  #[test]
563  fn custom_event() {
564    let tokens = quote! {
565      #[gui(Event = FooBarBazEvent)]
566      struct Bar { }
567    };
568
569    let input = parse2::<DeriveInput>(tokens).unwrap();
570    let (new, event, message) = parse_attributes(&input.attrs).unwrap();
571    assert_eq!(new, None);
572    assert_eq!(message, None);
573
574    let tokens = quote! { FooBarBazEvent };
575    let foobar = parse2::<Type>(tokens).unwrap();
576    assert_eq!(event, Some(foobar));
577  }
578
579  /// Test that we can handle the `Message` attribute properly.
580  #[test]
581  fn custom_message() {
582    let tokens = quote! {
583      #[gui(Message = SomeMessage)]
584      struct Foo { }
585    };
586
587    let input = parse2::<DeriveInput>(tokens).unwrap();
588    let (new, event, message) = parse_attributes(&input.attrs).unwrap();
589    assert_eq!(new, None);
590    assert_eq!(event, None);
591
592    let tokens = quote! { SomeMessage };
593    let some_message = parse2::<Type>(tokens).unwrap();
594    assert_eq!(message, Some(some_message));
595  }
596
597  /// Test that we can handle both the `Event` and `Message` attributes
598  /// properly together.
599  #[test]
600  fn custom_event_and_message() {
601    let tokens = quote! {
602      #[gui(Event = FooBar, Message = FooBaz)]
603      struct Foo { }
604    };
605
606    let input = parse2::<DeriveInput>(tokens).unwrap();
607    let (new, event, message) = parse_attributes(&input.attrs).unwrap();
608    assert_eq!(new, None);
609
610    let tokens = quote! { FooBar };
611    let foobar = parse2::<Type>(tokens).unwrap();
612    assert_eq!(event, Some(foobar));
613
614    let tokens = quote! { FooBaz };
615    let foobaz = parse2::<Type>(tokens).unwrap();
616    assert_eq!(message, Some(foobaz));
617  }
618
619  #[test]
620  fn default_new_and_event_with_ignore() {
621    let tokens = quote! {
622      #[gui(default_new, Event = ())]
623      struct Baz { }
624    };
625
626    let input = parse2::<DeriveInput>(tokens).unwrap();
627    let (new, event, message) = parse_attributes(&input.attrs).unwrap();
628    assert_eq!(new, Some(()));
629    assert_eq!(message, None);
630
631    let tokens = quote! { () };
632    let parens = parse2::<Type>(tokens).unwrap();
633    assert_eq!(event, Some(parens));
634  }
635
636  #[test]
637  fn last_event_type_takes_precedence() {
638    let tokens = quote! {
639      #[gui(Event = Event1)]
640      #[gui(Event = Event2)]
641      struct Foo { }
642    };
643
644    let input = parse2::<DeriveInput>(tokens).unwrap();
645    let (_, event, _) = parse_attributes(&input.attrs).unwrap();
646
647    let tokens = quote! { Event2 };
648    let event2 = parse2::<Type>(tokens).unwrap();
649    assert_eq!(event, Some(event2));
650  }
651}