Skip to content

Commit

Permalink
[Macros] Reduce generated code for field conversions (#260)
Browse files Browse the repository at this point in the history
* [Macros] Reduce generated code for field conversions

* Avoid generating CooldownConfig initialisation entirely if default
  • Loading branch information
GnomedDev authored May 4, 2024
1 parent 186897a commit 5b369bb
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 40 deletions.
62 changes: 42 additions & 20 deletions macros/src/command/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod prefix;
mod slash;

use crate::util::{wrap_option, wrap_option_to_string};
use crate::util::{
iter_tuple_2_to_hash_map, wrap_option, wrap_option_and_map, wrap_option_to_string,
};
use proc_macro::TokenStream;
use syn::spanned::Spanned as _;

Expand Down Expand Up @@ -271,20 +273,14 @@ fn generate_command(mut inv: Invocation) -> Result<proc_macro2::TokenStream, dar
.rename
.clone()
.unwrap_or_else(|| function_name.clone());

let context_menu_name = wrap_option_to_string(inv.args.context_menu_command.as_ref());

let description = match &inv.description {
Some(x) => quote::quote! { Some(#x.to_string()) },
None => quote::quote! { None },
};
let hide_in_help = &inv.args.hide_in_help;
let description = wrap_option_to_string(inv.description.as_ref());
let category = wrap_option_to_string(inv.args.category.as_ref());

let global_cooldown = wrap_option(inv.args.global_cooldown);
let user_cooldown = wrap_option(inv.args.user_cooldown);
let guild_cooldown = wrap_option(inv.args.guild_cooldown);
let channel_cooldown = wrap_option(inv.args.channel_cooldown);
let member_cooldown = wrap_option(inv.args.member_cooldown);
let cooldown_config = generate_cooldown_config(&inv.args);

let default_member_permissions = &inv.default_member_permissions;
let required_permissions = &inv.required_permissions;
Expand Down Expand Up @@ -324,9 +320,9 @@ fn generate_command(mut inv: Invocation) -> Result<proc_macro2::TokenStream, dar
None => quote::quote! { Box::new(()) },
};

let name_localizations = crate::util::vec_tuple_2_to_hash_map(inv.args.name_localized);
let name_localizations = iter_tuple_2_to_hash_map(inv.args.name_localized.into_iter());
let description_localizations =
crate::util::vec_tuple_2_to_hash_map(inv.args.description_localized);
iter_tuple_2_to_hash_map(inv.args.description_localized.into_iter());

let function_ident =
std::mem::replace(&mut inv.function.sig.ident, syn::parse_quote! { inner });
Expand Down Expand Up @@ -359,14 +355,7 @@ fn generate_command(mut inv: Invocation) -> Result<proc_macro2::TokenStream, dar
help_text: #help_text,
hide_in_help: #hide_in_help,
cooldowns: std::sync::Mutex::new(::poise::Cooldowns::new()),
cooldown_config: std::sync::RwLock::new(::poise::CooldownConfig {
global: #global_cooldown.map(std::time::Duration::from_secs),
user: #user_cooldown.map(std::time::Duration::from_secs),
guild: #guild_cooldown.map(std::time::Duration::from_secs),
channel: #channel_cooldown.map(std::time::Duration::from_secs),
member: #member_cooldown.map(std::time::Duration::from_secs),
__non_exhaustive: ()
}),
cooldown_config: #cooldown_config,
reuse_response: #reuse_response,
default_member_permissions: #default_member_permissions,
required_permissions: #required_permissions,
Expand All @@ -393,3 +382,36 @@ fn generate_command(mut inv: Invocation) -> Result<proc_macro2::TokenStream, dar
}
})
}

fn generate_cooldown_config(args: &CommandArgs) -> proc_macro2::TokenStream {
let all_cooldowns = [
args.global_cooldown,
args.user_cooldown,
args.guild_cooldown,
args.channel_cooldown,
args.member_cooldown,
];

if all_cooldowns.iter().all(Option::is_none) {
return quote::quote!(std::sync::RwLock::default());
}

let to_seconds_path = quote::quote!(std::time::Duration::from_secs);

let global_cooldown = wrap_option_and_map(args.global_cooldown, &to_seconds_path);
let user_cooldown = wrap_option_and_map(args.user_cooldown, &to_seconds_path);
let guild_cooldown = wrap_option_and_map(args.guild_cooldown, &to_seconds_path);
let channel_cooldown = wrap_option_and_map(args.channel_cooldown, &to_seconds_path);
let member_cooldown = wrap_option_and_map(args.member_cooldown, &to_seconds_path);

quote::quote!(
std::sync::RwLock::new(::poise::CooldownConfig {
global: #global_cooldown,
user: #user_cooldown,
guild: #guild_cooldown,
channel: #channel_cooldown,
member: #member_cooldown,
__non_exhaustive: ()
})
)
}
25 changes: 10 additions & 15 deletions macros/src/command/slash.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use super::Invocation;
use crate::util::extract_type_parameter;
use crate::util::{
extract_type_parameter, iter_tuple_2_to_hash_map, tuple_2_iter_deref, wrap_option_to_string,
};
use quote::format_ident;
use syn::spanned::Spanned as _;

Expand All @@ -8,10 +10,7 @@ pub fn generate_parameters(inv: &Invocation) -> Result<Vec<proc_macro2::TokenStr
for param in &inv.parameters {
// no #[description] check here even if slash_command set, so users can programatically
// supply descriptions later (e.g. via translation framework like fluent)
let description = match &param.args.description {
Some(x) => quote::quote! { Some(#x.to_string()) },
None => quote::quote! { None },
};
let description = wrap_option_to_string(param.args.description.as_ref());

let (mut required, type_) = match extract_type_parameter("Option", &param.type_)
.or_else(|| extract_type_parameter("Vec", &param.type_))
Expand All @@ -26,10 +25,10 @@ pub fn generate_parameters(inv: &Invocation) -> Result<Vec<proc_macro2::TokenStr
}

let param_name = &param.name;
let name_locales = param.args.name_localized.iter().map(|x| &x.0);
let name_localized_values = param.args.name_localized.iter().map(|x| &x.1);
let description_locales = param.args.description_localized.iter().map(|x| &x.0);
let description_localized_values = param.args.description_localized.iter().map(|x| &x.1);
let name_localizations =
iter_tuple_2_to_hash_map(tuple_2_iter_deref(&param.args.name_localized));
let desc_localizations =
iter_tuple_2_to_hash_map(tuple_2_iter_deref(&param.args.description_localized));

let autocomplete_callback = match &param.args.autocomplete {
Some(autocomplete_fn) => {
Expand Down Expand Up @@ -118,13 +117,9 @@ pub fn generate_parameters(inv: &Invocation) -> Result<Vec<proc_macro2::TokenStr
quote::quote! {
::poise::CommandParameter {
name: #param_name.to_string(),
name_localizations: vec![
#( (#name_locales.to_string(), #name_localized_values.to_string()) ),*
].into_iter().collect(),
name_localizations: #name_localizations,
description: #description,
description_localizations: vec![
#( (#description_locales.to_string(), #description_localized_values.to_string()) ),*
].into_iter().collect(),
description_localizations: #desc_localizations,
required: #required,
channel_types: #channel_types,
type_setter: #type_setter,
Expand Down
42 changes: 37 additions & 5 deletions macros/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@ pub fn wrap_option<T: quote::ToTokens>(literal: Option<T>) -> syn::Expr {
}
}

/// Converts None => `None` and Some(x) => `Some(#x.to_string())`
pub fn wrap_option_to_string<T: quote::ToTokens>(literal: Option<T>) -> syn::Expr {
/// Converts None => `None` and Some(x) => `Some(#map_path(#x))`
pub fn wrap_option_and_map<T: quote::ToTokens>(
literal: Option<T>,
map_path: impl quote::ToTokens,
) -> syn::Expr {
match literal {
Some(literal) => syn::parse_quote! { Some(#literal.to_string()) },
Some(literal) => syn::parse_quote! { Some(#map_path(#literal)) },
None => syn::parse_quote! { None },
}
}

pub fn wrap_option_to_string<T: quote::ToTokens>(literal: Option<T>) -> syn::Expr {
let to_string_path = quote::quote!(::std::string::ToString::to_string);
wrap_option_and_map(literal, to_string_path)
}

/// Syn Fold to make all lifetimes 'static. Used to access trait items of a type without having its
/// concrete lifetime available
pub struct AllLifetimesToStatic;
Expand Down Expand Up @@ -79,8 +87,32 @@ impl<T: darling::FromMeta> darling::FromMeta for Tuple2<T> {
}
}

pub fn vec_tuple_2_to_hash_map(v: Vec<Tuple2<String>>) -> proc_macro2::TokenStream {
let (keys, values): (Vec<String>, Vec<String>) = v.into_iter().map(|x| (x.0, x.1)).unzip();
pub fn tuple_2_iter_deref<'a, I: 'a, T: 'a, D: ?Sized + 'a>(
iter: I,
) -> impl ExactSizeIterator<Item = Tuple2<&'a D>>
where
I: IntoIterator<Item = &'a Tuple2<T>>,
I::IntoIter: ExactSizeIterator,
T: std::ops::Deref<Target = D>,
{
iter.into_iter()
.map(|Tuple2(t, v)| Tuple2(t.deref(), v.deref()))
}

pub fn iter_tuple_2_to_hash_map<I, T>(v: I) -> proc_macro2::TokenStream
where
I: ExactSizeIterator<Item = Tuple2<T>>,
T: quote::ToTokens,
{
if v.len() == 0 {
return quote::quote!(std::collections::HashMap::new());
}

let (keys, values) = v
.into_iter()
.map(|x| (x.0, x.1))
.unzip::<_, _, Vec<_>, Vec<_>>();

quote::quote! {
std::collections::HashMap::from([
#( (#keys.to_string(), #values.to_string()) ),*
Expand Down

0 comments on commit 5b369bb

Please sign in to comment.