#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
#![forbid(unsafe_code)]
#![warn(
clippy::pedantic,
clippy::nursery,
// Restriction lints
clippy::clone_on_ref_ptr,
clippy::create_dir,
clippy::dbg_macro,
clippy::decimal_literal_representation,
clippy::exit,
clippy::float_cmp_const,
clippy::get_unwrap,
clippy::let_underscore_must_use,
clippy::map_err_ignore,
clippy::mem_forget,
clippy::missing_docs_in_private_items,
clippy::multiple_inherent_impl,
clippy::panic_in_result_fn,
clippy::print_stderr,
clippy::print_stdout,
clippy::rest_pat_in_fully_bound_structs,
clippy::str_to_string,
clippy::string_to_string,
clippy::todo,
clippy::unneeded_field_pattern,
clippy::use_debug,
)]
#![allow(
clippy::suboptimal_flops,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
clippy::module_name_repetitions,
clippy::missing_panics_doc
)]
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_derive(Focus, attributes(focus))]
pub fn focus_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_focus(&ast)
}
fn impl_focus(ast: &syn::DeriveInput) -> TokenStream {
let ident = &ast.ident;
let generics = &ast.generics;
match ast.data {
syn::Data::Struct(ref s) => impl_focus_struct(ident, generics, s),
syn::Data::Enum(ref e) => impl_focus_enum(ident, generics, e),
syn::Data::Union(ref _u) => unimplemented!("Unions are currently not supported."),
}
}
fn impl_focus_struct(
ident: &syn::Ident,
generics: &syn::Generics,
s: &syn::DataStruct,
) -> TokenStream {
let (fields, len) = match s.fields {
syn::Fields::Named(ref named) => {
(FocusField::collect_fields_named(named), named.named.len())
}
syn::Fields::Unnamed(ref unnamed) => (
FocusField::collect_fields_unnamed(unnamed),
unnamed.unnamed.len(),
),
syn::Fields::Unit => unimplemented!("Unit structs are currently not supported."),
};
build_focus_trait_for_struct(ident, generics, &fields, len)
}
fn build_focus_trait_for_struct<'a>(
ident: &syn::Ident,
generics: &syn::Generics,
fields: &[FocusField<'a>],
len: usize,
) -> TokenStream {
let array_name = quote! {fields};
let focus_method_body = build_focus_method_body(0, &array_name, fields, len, true);
let has_focus_method_body = build_has_focus_method_body(fields, true);
let generic_idents = generic_idents(generics);
let result = quote! {
impl#generics iced_focus::Focus for #ident#generic_idents {
fn focus(&mut self, direction: iced_focus::Direction) -> iced_focus::State {
#focus_method_body
}
fn has_focus(&self) -> bool {
#has_focus_method_body
}
}
};
result.into()
}
fn impl_focus_enum(ident: &syn::Ident, generics: &syn::Generics, e: &syn::DataEnum) -> TokenStream {
let variants = &e.variants;
let method_bodies: Vec<(proc_macro2::TokenStream, proc_macro2::TokenStream)> = variants
.iter()
.enumerate()
.map(|(index, variant)| impl_focus_enum_variant(index, variant))
.collect();
let focus_bodies = method_bodies.iter().map(|(focus, _)| focus);
let has_focus_bodies = method_bodies.iter().map(|(_, has_focus)| has_focus);
let booleans: Vec<proc_macro2::TokenStream> = variants
.iter()
.enumerate()
.map(|(index, variant)| {
let fields = match variant.fields {
syn::Fields::Named(ref named) => FocusField::collect_fields_named(named),
syn::Fields::Unnamed(ref unnamed) => FocusField::collect_fields_unnamed(unnamed),
syn::Fields::Unit => Vec::new(),
};
let booleans = fields
.iter()
.map(|field| (field.index, &field.attribute))
.filter_map(|(field_index, attribute)| match attribute {
FocusAttribute::Enable(_) => None,
FocusAttribute::EnableWith(_, _) => {
Some(attribute.to_boolean_expression(field_index, Some(index)))
}
});
quote! {
#(#booleans)*
}
})
.collect();
let generic_idents = generic_idents(generics);
let result = quote! {
impl#generics iced_focus::Focus for #ident#generic_idents {
fn focus(&mut self, direction: iced_focus::Direction) -> iced_focus::State {
#(#booleans)*
match self {
#(#focus_bodies)*
}
}
fn has_focus(&self) -> bool {
match self {
#(#has_focus_bodies)*
}
}
}
};
result.into()
}
fn impl_focus_enum_variant(
index: usize,
variant: &syn::Variant,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let ident = &variant.ident;
let array_name = quote! {fields};
let (fields, len) = match variant.fields {
syn::Fields::Named(ref named) => {
(FocusField::collect_fields_named(named), named.named.len())
}
syn::Fields::Unnamed(ref unnamed) => (
FocusField::collect_fields_unnamed(unnamed),
unnamed.unnamed.len(),
),
syn::Fields::Unit => (Vec::new(), 0),
};
let field_idents: Vec<proc_macro2::TokenStream> =
fields.iter().map(|field| field.ident(false)).collect();
let focus_method_body = build_focus_method_body(index, &array_name, &fields, len, false);
let has_focus_method_body = build_has_focus_method_body(&fields, false);
let variant_fields = match variant.fields {
syn::Fields::Named(_) => quote! { {#(#field_idents,)* ..} },
syn::Fields::Unnamed(ref unnamed) => {
let idents = (0..unnamed.unnamed.len())
.into_iter()
.map(|i| syn::Ident::new(&format!("t_{}", i), proc_macro2::Span::call_site()));
quote! { (#(#idents,)*) }
}
syn::Fields::Unit => quote! {},
};
let focus_method_body = quote! {
Self::#ident #variant_fields => {
#focus_method_body
}
};
let has_focus_method_body = quote! {
Self::#ident #variant_fields => {
#has_focus_method_body
}
};
(focus_method_body, has_focus_method_body)
}
fn build_focus_method_body<'a>(
index: usize,
array_name: &proc_macro2::TokenStream,
fields: &[FocusField<'a>],
len: usize,
with_self: bool,
) -> proc_macro2::TokenStream {
let field_to_vector: Vec<proc_macro2::TokenStream> = fields
.iter()
.map(|field| {
if with_self {
field.add_struct_field_to_array(array_name)
} else {
field.add_enum_field_to_array(index, array_name)
}
})
.collect();
let booleans: Vec<proc_macro2::TokenStream> = if with_self {
fields
.iter()
.map(|field| field.attribute.to_boolean_expression(field.index, None))
.collect()
} else {
Vec::new()
};
let array_init = std::iter::repeat(quote! { None }).take(len);
quote! {
let mut #array_name: [Option<&mut dyn iced_focus::Focus>; #len] = [#(#array_init,)*];
#(#booleans)*
#(#field_to_vector)*
#array_name.focus(direction)
}
}
fn build_has_focus_method_body(
fields: &[FocusField<'_>],
with_self: bool,
) -> proc_macro2::TokenStream {
let field_idents: Vec<proc_macro2::TokenStream> =
fields.iter().map(|field| field.ident(with_self)).collect();
let booleans: Vec<proc_macro2::TokenStream> = fields
.iter()
.map(|field| match field.attribute {
FocusAttribute::Enable(_) => quote! {},
FocusAttribute::EnableWith(_, ref path) => quote! {#path() &&},
})
.collect();
let with_self = if with_self {
quote! {self.}
} else {
quote! {}
};
quote! {
#(#booleans #with_self#field_idents.has_focus() ||)* false
}
}
fn generic_idents(generics: &syn::Generics) -> proc_macro2::TokenStream {
let generics: Vec<proc_macro2::TokenStream> = generics
.params
.iter()
.map(|param| match param {
syn::GenericParam::Type(ty) => {
let ident = &ty.ident;
quote! { #ident }
}
syn::GenericParam::Lifetime(lifetime) => {
let lifetime = &lifetime.lifetime;
quote! { #lifetime }
}
syn::GenericParam::Const(con) => {
let ident = &con.ident;
quote! { #ident }
}
})
.collect();
if generics.is_empty() {
quote! {}
} else {
quote! { <#(#generics,)*> }
}
}
#[derive(Debug)]
struct FocusField<'a> {
ident: proc_macro2::TokenStream,
index: usize,
unnamed: bool,
attribute: FocusAttribute<'a>,
}
impl<'a> FocusField<'a> {
fn collect_fields_named(fields_named: &'a syn::FieldsNamed) -> Vec<Self> {
fields_named
.named
.iter()
.enumerate()
.filter_map(|(index, field)| FocusField::from_field_if_annotated(field, index))
.collect()
}
fn collect_fields_unnamed(fields_unnamed: &'a syn::FieldsUnnamed) -> Vec<Self> {
fields_unnamed
.unnamed
.iter()
.enumerate()
.filter_map(|(index, field)| FocusField::from_field_if_annotated(field, index))
.collect()
}
fn from_field_if_annotated(field: &'a syn::Field, index: usize) -> Option<Self> {
let attribute = FocusAttribute::extract_focus_attribute(&field.attrs);
let index_literal = proc_macro2::Literal::usize_unsuffixed(index);
attribute.map(|attribute| Self {
ident: if let Some(ident) = field.ident.as_ref() {
quote! {#ident}
} else {
quote! {#index_literal}
},
index,
unnamed: field.ident.is_none(),
attribute,
})
}
fn add_struct_field_to_array(
&self,
array_name: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let ident = self.ident(true);
let index = self.index;
match self.attribute {
FocusAttribute::Enable(_) => quote! {
#array_name[#index] = Some(&mut self.#ident);
},
FocusAttribute::EnableWith(_, _) => {
let boolean =
syn::Ident::new(&format!("b_{}", self.index), proc_macro2::Span::call_site());
quote! {
if #boolean {
#array_name[#index] = Some(&mut self.#ident);
}
}
}
}
}
fn add_enum_field_to_array(
&self,
index: usize,
array_name: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let ident = self.ident(false);
let field_index = self.index;
match self.attribute {
FocusAttribute::Enable(_) => quote! {
#array_name[#field_index] = Some(#ident);
},
FocusAttribute::EnableWith(_, _) => {
let boolean = syn::Ident::new(
&format!("b_{}_{}", index, self.index),
proc_macro2::Span::call_site(),
);
quote! {
if #boolean {
#array_name[#field_index] = Some(#ident);
}
}
}
}
}
fn ident(&self, with_self: bool) -> proc_macro2::TokenStream {
if !with_self && self.unnamed {
let tmp = syn::Ident::new(&format!("t_{}", self.ident), proc_macro2::Span::call_site());
quote! {#tmp}
} else {
self.ident.clone()
}
}
}
#[derive(Debug)]
enum FocusAttribute<'a> {
Enable(&'a syn::Ident),
EnableWith(&'a syn::Ident, proc_macro2::TokenStream),
}
impl<'a> FocusAttribute<'a> {
fn extract_focus_attribute(attrs: &'a [syn::Attribute]) -> Option<Self> {
let attr: Option<(&syn::PathSegment, syn::MetaList)> = attrs
.iter()
.filter_map(|attr| {
match attr.parse_meta() {
Ok(syn::Meta::List(meta)) => Some((&attr.path.segments, meta)),
Ok(_) | Err(_) => None,
}
})
.find_map(|(path, meta)| {
path.iter()
.find(|s| s.ident == "focus")
.map(|path| (path, meta))
});
attr.map(|(path, mut meta)| {
if meta.nested.len() != 1 {
panic!("Expected the focus attribute to be not empty.");
}
match meta.nested.pop().unwrap().into_value() {
syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
let _ = nv
.path
.get_ident()
.filter(|ident| *ident == "enable")
.expect("Expected the ident `enable` inside the focus attribute.");
match nv.lit {
syn::Lit::Str(s) => {
let p: proc_macro2::TokenStream = syn::parse_str(&s.value()).unwrap();
FocusAttribute::EnableWith(&path.ident, p)
}
_ => panic!(
"Expected the path of `focus(enable = PATH) to be a `str` literal."
),
}
}
syn::NestedMeta::Meta(syn::Meta::Path(p)) => {
let _ = p
.get_ident()
.filter(|ident| *ident == "enable")
.expect("Expected the ident `enable` inside the focus attribute.");
FocusAttribute::Enable(&path.ident)
}
_ => panic!(
"The nested meta of the focus attribute must be `enable` or `enable = PATH`."
),
}
})
}
fn to_boolean_expression(
&self,
field_index: usize,
variant_index: Option<usize>,
) -> proc_macro2::TokenStream {
match self {
FocusAttribute::Enable(_) => quote! {},
FocusAttribute::EnableWith(_, ref path) => {
let boolean = match variant_index {
Some(variant_index) => syn::Ident::new(
&format!("b_{}_{}", variant_index, field_index),
proc_macro2::Span::call_site(),
),
None => syn::Ident::new(
&format!("b_{}", field_index),
proc_macro2::Span::call_site(),
),
};
quote! {
let #boolean = #path();
}
}
}
}
}