#![warn(
future_incompatible,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
rust_2018_compatibility,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications,
unused_results,
)]
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use proc_macro::LexError;
use proc_macro::TokenStream;
use proc_macro2::Ident;
use proc_macro2::Span;
use proc_macro2::TokenStream as Tokens;
use quote::quote;
use syn::Attribute;
use syn::Binding;
use syn::Data;
use syn::DeriveInput;
use syn::Fields;
use syn::GenericParam;
use syn::Generics;
use syn::parenthesized;
use syn::parse::Parse;
use syn::parse::ParseStream;
use syn::parse2;
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::token::Eq;
use syn::Type;
use syn::TypeGenerics;
use syn::WhereClause;
use syn::WherePredicate;
type New = Option<()>;
type Event = Option<Type>;
type Message = Option<Type>;
#[derive(Debug)]
enum Error {
Error(String),
LexError(LexError),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match *self {
Error::Error(ref e) => write!(f, "{}", e),
Error::LexError(ref e) => write!(f, "{:?}", e),
}
}
}
impl From<String> for Error {
fn from(string: String) -> Error {
Error::Error(string)
}
}
impl From<&'static str> for Error {
fn from(string: &'static str) -> Error {
Error::Error(string.to_string())
}
}
impl From<LexError> for Error {
fn from(error: LexError) -> Error {
Error::LexError(error)
}
}
type Result<T> = std::result::Result<T, Error>;
#[proc_macro_derive(Widget, attributes(gui))]
pub fn widget(input: TokenStream) -> TokenStream {
match expand_widget(input) {
Ok(tokens) => tokens,
Err(error) => panic!("{}", error),
}
}
fn expand_widget(input: TokenStream) -> Result<TokenStream> {
let input = parse2::<DeriveInput>(input.into()).map_err(|_| "unable to parse input")?;
let (new, event, message) = parse_attributes(&input.attrs)?;
let tokens = expand_widget_input(new, &event, &message, &input)?;
Ok(tokens.into())
}
fn parse_attributes(attributes: &[Attribute]) -> Result<(New, Event, Message)> {
let (new, event, message) = attributes
.iter()
.map(|attr| parse_attribute(attr))
.fold(Ok((None, None, None)), |result1, result2| {
match (result1, result2) {
(Ok((new1, event1, message1)), Ok((new2, event2, message2))) => {
Ok((new2.or(new1), event2.or(event1), message2.or(message1)))
},
(Err(x), _) | (_, Err(x)) => Err(x),
}
})?;
Ok((new, event, message))
}
fn parse_gui_attribute(item: Attr) -> Result<(New, Event, Message)> {
match item {
Attr::Ident(ref ident) if ident == "default_new" => {
Ok((Some(()), None, None))
},
Attr::Binding(binding) => {
if binding.ident == "Event" {
Ok((None, Some(binding.ty), None))
} else if binding.ident == "Message" {
Ok((None, None, Some(binding.ty)))
} else {
Err(Error::from("encountered unknown binding attribute"))
}
},
_ => Err(Error::from("encountered unknown attribute")),
}
}
fn parse_gui_attributes(list: AttrList) -> Result<(New, Event, Message)> {
let mut new = None;
let mut event = None;
let mut message = None;
for item in list.0 {
let (this_new, this_event, this_message) = parse_gui_attribute(item)?;
new = this_new.or(new);
event = this_event.or(event);
message = this_message.or(message);
}
Ok((new, event, message))
}
struct AttrList(Punctuated<Attr, Comma>);
impl Parse for AttrList {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let content;
let _ = parenthesized!(content in input);
let list = content.parse_terminated(Attr::parse)?;
Ok(Self(list))
}
}
#[allow(clippy::large_enum_variant)]
enum Attr {
Ident(Ident),
Binding(Binding),
}
impl Parse for Attr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
if input.peek2(Eq) {
let bind = input.parse::<Binding>()?;
Ok(Attr::Binding(bind))
} else {
input.parse::<Ident>().map(Attr::Ident)
}
}
}
fn parse_attribute(attribute: &Attribute) -> Result<(New, Event, Message)> {
if attribute.path.is_ident("gui") {
let tokens = attribute.tokens.clone();
let attr = parse2::<AttrList>(tokens).map_err(|err| {
format!("unable to parse attributes: {:?}", err)
})?;
parse_gui_attributes(attr)
} else {
Ok((None, None, None))
}
}
fn expand_widget_input(
new: New,
event: &Event,
message: &Message,
input: &DeriveInput,
) -> Result<Tokens> {
match input.data {
Data::Struct(ref data) => {
check_struct_fields(&data.fields)?;
Ok(expand_widget_traits(new, event, message, input))
},
_ => Err(Error::from("#[derive(Widget)] is only defined for structs")),
}
}
fn check_struct_fields(fields: &Fields) -> Result<()> {
let id = ("id", "::gui::Id");
for (req_field, req_type) in &[id] {
let _ = fields
.iter()
.find(|field| {
if let Some(ref ident) = field.ident {
ident == req_field
} else {
false
}
})
.ok_or_else(|| Error::from(format!("struct field {}: {} not found", req_field, req_type)))?;
}
Ok(())
}
fn expand_widget_traits(new: New, event: &Event, message: &Message, input: &DeriveInput) -> Tokens {
let new_impl = expand_new_impl(new, input);
let renderable = expand_renderable_trait(input);
let object = expand_object_trait(input);
let widget = expand_widget_trait(event, message, input);
quote! {
#new_impl
#renderable
#object
#widget
}
}
fn expand_new_impl(new: New, input: &DeriveInput) -> Tokens {
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
match new {
Some(..) => {
quote! {
#[allow(dead_code)]
impl #impl_generics #name #ty_generics #where_clause {
pub fn new(id: ::gui::Id) -> Self {
#name {
id,
}
}
}
}
},
None => quote! {},
}
}
fn expand_renderable_trait(input: &DeriveInput) -> Tokens {
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
quote! {
impl #impl_generics ::gui::Renderable for #name #ty_generics #where_clause {
fn type_id(&self) -> ::std::any::TypeId {
::std::any::TypeId::of::<#name #ty_generics>()
}
fn render(
&self,
cap: &::gui::Cap,
renderer: &::gui::Renderer,
bbox: ::gui::BBox,
) -> ::gui::BBox {
renderer.render(self, cap, bbox)
}
fn render_done(
&self,
cap: &::gui::Cap,
renderer: &::gui::Renderer,
bbox: ::gui::BBox,
) {
renderer.render_done(self, cap, bbox)
}
}
}
}
fn expand_object_trait(input: &DeriveInput) -> Tokens {
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
quote! {
impl #impl_generics ::gui::Object for #name #ty_generics #where_clause {
fn id(&self) -> ::gui::Id {
self.id
}
}
}
}
fn expand_widget_trait(event: &Event, message: &Message, input: &DeriveInput) -> Tokens {
let name = &input.ident;
let (generics, ty_generics, where_clause) = split_for_impl(&input.generics, event, message);
let event = if let Some(event) = event {
quote! { #event }
} else {
let ident = Ident::new("__E", Span::call_site());
quote! { #ident }
};
let message = if let Some(message) = message {
quote! { #message }
} else {
let ident = Ident::new("__M", Span::call_site());
quote! { #ident }
};
let widget = quote! { ::gui::Widget<#event, #message> };
quote! {
impl #generics #widget for #name #ty_generics #where_clause {
fn type_id(&self) -> ::std::any::TypeId {
::std::any::TypeId::of::<#name #ty_generics>()
}
}
}
}
#[proc_macro_derive(Handleable, attributes(gui))]
pub fn handleable(input: TokenStream) -> TokenStream {
match expand_handleable(input) {
Ok(tokens) => tokens,
Err(error) => panic!("{}", error),
}
}
fn expand_handleable(input: TokenStream) -> Result<TokenStream> {
let input = parse2::<DeriveInput>(input.into()).map_err(|_| "unable to parse input")?;
let (_, event, message) = parse_attributes(&input.attrs)?;
let tokens = expand_handleable_input(&event, &message, &input)?;
Ok(tokens.into())
}
fn expand_handleable_input(
event: &Event,
message: &Message,
input: &DeriveInput,
) -> Result<Tokens> {
match input.data {
Data::Struct(_) => Ok(expand_handleable_trait(event, message, input)),
_ => Err(Error::from("#[derive(Handleable)] is only defined for structs")),
}
}
fn extend_generics(generics: &Generics, ident: Ident) -> Generics {
let param = GenericParam::Type(ident.into());
let mut generics = generics.clone();
generics.params.push(param);
generics
}
fn extend_where_clause(where_clause: &Option<WhereClause>, ident: &Ident) -> WhereClause {
if let Some(where_clause) = where_clause {
let predicate = quote! { #ident: 'static };
let predicate = parse2::<WherePredicate>(predicate).unwrap();
let mut where_clause = where_clause.clone();
where_clause.predicates.push(predicate);
where_clause
} else {
let where_clause = quote! { where #ident: 'static };
parse2::<WhereClause>(where_clause).unwrap()
}
}
fn split_for_impl<'g>(
generics: &'g Generics,
event: &Event,
message: &Message,
) -> (Generics, TypeGenerics<'g>, Option<WhereClause>) {
let (_, ty_generics, _) = generics.split_for_impl();
let generics = generics.clone();
let where_clause = generics.where_clause.clone();
let (generics, where_clause) = if event.is_none() {
let ident = Ident::new("__E", Span::call_site());
let generics = extend_generics(&generics, ident.clone());
let where_clause = extend_where_clause(&where_clause, &ident);
(generics, Some(where_clause))
} else {
(generics, where_clause)
};
let (generics, where_clause) = if message.is_none() {
let ident = Ident::new("__M", Span::call_site());
let generics = extend_generics(&generics, ident.clone());
let where_clause = extend_where_clause(&where_clause, &ident);
(generics, Some(where_clause))
} else {
(generics, where_clause)
};
(generics, ty_generics, where_clause)
}
fn expand_handleable_trait(event: &Event, message: &Message, input: &DeriveInput) -> Tokens {
let name = &input.ident;
let (generics, ty_generics, where_clause) = split_for_impl(&input.generics, event, message);
let event = if let Some(event) = event {
quote! { #event }
} else {
let ident = Ident::new("__E", Span::call_site());
quote! { #ident }
};
let message = if let Some(message) = message {
quote! { #message }
} else {
let ident = Ident::new("__M", Span::call_site());
quote! { #ident }
};
let handleable = quote! { ::gui::Handleable<#event, #message> };
quote! {
impl #generics #handleable for #name #ty_generics #where_clause {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_widget_attributes() {
let tokens = quote! {
struct Bar { }
};
let input = parse2::<DeriveInput>(tokens).unwrap();
let (new, event, message) = parse_attributes(&input.attrs).unwrap();
assert_eq!(new, None);
assert_eq!(event, None);
assert_eq!(message, None);
}
#[test]
fn default_new() {
let tokens = quote! {
#[gui(default_new)]
struct Bar { }
};
let input = parse2::<DeriveInput>(tokens).unwrap();
let (new, event, message) = parse_attributes(&input.attrs).unwrap();
assert_eq!(new, Some(()));
assert_eq!(event, None);
assert_eq!(message, None);
}
#[test]
fn custom_event() {
let tokens = quote! {
#[gui(Event = FooBarBazEvent)]
struct Bar { }
};
let input = parse2::<DeriveInput>(tokens).unwrap();
let (new, event, message) = parse_attributes(&input.attrs).unwrap();
assert_eq!(new, None);
assert_eq!(message, None);
let tokens = quote! { FooBarBazEvent };
let foobar = parse2::<Type>(tokens).unwrap();
assert_eq!(event, Some(foobar));
}
#[test]
fn custom_message() {
let tokens = quote! {
#[gui(Message = SomeMessage)]
struct Foo { }
};
let input = parse2::<DeriveInput>(tokens).unwrap();
let (new, event, message) = parse_attributes(&input.attrs).unwrap();
assert_eq!(new, None);
assert_eq!(event, None);
let tokens = quote! { SomeMessage };
let some_message = parse2::<Type>(tokens).unwrap();
assert_eq!(message, Some(some_message));
}
#[test]
fn custom_event_and_message() {
let tokens = quote! {
#[gui(Event = FooBar, Message = FooBaz)]
struct Foo { }
};
let input = parse2::<DeriveInput>(tokens).unwrap();
let (new, event, message) = parse_attributes(&input.attrs).unwrap();
assert_eq!(new, None);
let tokens = quote! { FooBar };
let foobar = parse2::<Type>(tokens).unwrap();
assert_eq!(event, Some(foobar));
let tokens = quote! { FooBaz };
let foobaz = parse2::<Type>(tokens).unwrap();
assert_eq!(message, Some(foobaz));
}
#[test]
fn default_new_and_event_with_ignore() {
let tokens = quote! {
#[allow(an_attribute_to_be_ignored)]
#[gui(default_new, Event = ())]
struct Baz { }
};
let input = parse2::<DeriveInput>(tokens).unwrap();
let (new, event, message) = parse_attributes(&input.attrs).unwrap();
assert_eq!(new, Some(()));
assert_eq!(message, None);
let tokens = quote! { () };
let parens = parse2::<Type>(tokens).unwrap();
assert_eq!(event, Some(parens));
}
#[test]
fn last_event_type_takes_precedence() {
let tokens = quote! {
#[gui(Event = Event1)]
#[gui(Event = Event2)]
struct Foo { }
};
let input = parse2::<DeriveInput>(tokens).unwrap();
let (_, event, _) = parse_attributes(&input.attrs).unwrap();
let tokens = quote! { Event2 };
let event2 = parse2::<Type>(tokens).unwrap();
assert_eq!(event, Some(event2));
}
}