Skip to content

Commit

Permalink
Move value limitations away from type_setter
Browse files Browse the repository at this point in the history
  • Loading branch information
GnomedDev committed Nov 13, 2024
1 parent 6d2ae48 commit 101bc40
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 43 deletions.
100 changes: 58 additions & 42 deletions macros/src/command/slash.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use super::Invocation;
use super::{Invocation, ParamArgs};
use crate::util::{
extract_type_parameter, iter_tuple_2_to_vec_map, tuple_2_iter_deref, wrap_option_to_string,
extract_type_parameter, iter_tuple_2_to_vec_map, tuple_2_iter_deref, wrap_option,
wrap_option_to_string,
};
use quote::format_ident;
use quote::{format_ident, quote};
use syn::spanned::Spanned as _;

fn lit_to_string(lit: &syn::Lit) -> Result<String, syn::Error> {
Expand All @@ -20,6 +21,40 @@ fn lit_to_string(lit: &syn::Lit) -> Result<String, syn::Error> {
}
}

fn generate_value_limits(
args: &ParamArgs,
span: proc_macro2::Span,
) -> syn::Result<Option<proc_macro2::TokenStream>> {
let limits = match (&args.min, &args.max, &args.min_length, &args.max_length) {
(None, None, None, Some(max)) => {
quote!( ::poise::ValueLimits::Length { min: None, max: Some(#max) } )
}
(None, None, Some(min), None) => {
quote!( ::poise::ValueLimits::Length { min: Some(#min), max: None } )
}
(None, None, Some(min), Some(max)) => {
quote!( ::poise::ValueLimits::Length { min: Some(#min), max: Some(#max) } )
}
(None, Some(max), None, None) => {
quote!( ::poise::ValueLimits::Value { min: None, max: Some((#max) as f64) } )
}
(Some(min), None, None, None) => {
quote!( ::poise::ValueLimits::Value { min: Some((#min) as f64), max: None } )
}
(Some(min), Some(max), None, None) => {
quote!( ::poise::ValueLimits::Value { min: Some((#min) as f64), max: Some((#max) as f64) } )
}

(None, None, None, None) => return Ok(None),
_ => {
let err = "Cannot set both a `min_length/max_length` and a `min/max`";
return Err(syn::Error::new(span, err));
}
};

Ok(Some(limits))
}

pub fn generate_parameters(inv: &Invocation) -> Result<Vec<proc_macro2::TokenStream>, syn::Error> {
let mut parameter_structs = Vec::new();
for param in &inv.parameters {
Expand Down Expand Up @@ -47,82 +82,63 @@ pub fn generate_parameters(inv: &Invocation) -> Result<Vec<proc_macro2::TokenStr

let autocomplete_callback = match &param.args.autocomplete {
Some(autocomplete_fn) => {
quote::quote! { Some(|
quote! { Some(|
ctx: poise::ApplicationContext<'_, _, _>,
partial: &str,
| Box::pin(#autocomplete_fn(ctx.into(), partial))) }
}
None => quote::quote! { None },
None => quote! { None },
};

// We can just cast to f64 here because Discord only uses f64 precision anyways
// TODO: move this to poise::CommandParameter::{min, max} fields
let min_value_setter = match &param.args.min {
Some(x) => quote::quote! { .min_number_value(#x as f64) },
None => quote::quote! {},
};
let max_value_setter = match &param.args.max {
Some(x) => quote::quote! { .max_number_value(#x as f64) },
None => quote::quote! {},
};
// TODO: move this to poise::CommandParameter::{min_length, max_length} fields
let min_length_setter = match &param.args.min_length {
Some(x) => quote::quote! { .min_length(#x) },
None => quote::quote! {},
};
let max_length_setter = match &param.args.max_length {
Some(x) => quote::quote! { .max_length(#x) },
None => quote::quote! {},
};
let value_limits = wrap_option(generate_value_limits(&param.args, param.span)?);

let type_setter = match inv.args.slash_command {
true => {
if let Some(_choices) = &param.args.choices {
quote::quote! { Some(|o| o.kind(::poise::serenity_prelude::CommandOptionType::Integer)) }
quote! { Some(|o| o.kind(::poise::serenity_prelude::CommandOptionType::Integer)) }
} else {
quote::quote! { Some(|o| {
poise::create_slash_argument!(#type_, o)
#min_value_setter #max_value_setter
#min_length_setter #max_length_setter
}) }
quote! { Some(|o| poise::create_slash_argument!(#type_, o)) }
}
}
false => quote::quote! { None },
false => quote! { None },
};

// TODO: theoretically a problem that we don't store choices for non slash commands
// TODO: move this to poise::CommandParameter::choices (is there a reason not to?)
let choices = if inv.args.slash_command {
if let Some(choices) = &param.args.choices {
let choices_iter = choices.0.iter();
let choices: Vec<_> = choices_iter.map(lit_to_string).collect::<Result<_, _>>()?;

quote::quote! { Cow::Borrowed(&[#( ::poise::CommandParameterChoice {
quote! { Cow::Borrowed(&[#( ::poise::CommandParameterChoice {
name: Cow::Borrowed(#choices),
localizations: Cow::Borrowed(&[]),
__non_exhaustive: (),
} ),*]) }
} else {
quote::quote! { poise::slash_argument_choices!(#type_) }
quote! { poise::slash_argument_choices!(#type_) }
}
} else {
quote::quote! { Cow::Borrowed(&[]) }
quote! { Cow::Borrowed(&[]) }
};

let channel_types = match &param.args.channel_types {
Some(crate::util::List(channel_types)) => quote::quote! { Some(
Some(crate::util::List(channel_types)) => quote! { Some(
Cow::Borrowed(&[ #( poise::serenity_prelude::ChannelType::#channel_types ),* ])
) },
None => quote::quote! { None },
None => quote! { None },
};

parameter_structs.push((
quote::quote! {
quote! {
::poise::CommandParameter {
name: ::std::borrow::Cow::Borrowed(#param_name),
name_localizations: #name_localizations,
description: #description,
description_localizations: #desc_localizations,
required: #required,
channel_types: #channel_types,
value_limits: #value_limits,
type_setter: #type_setter,
choices: #choices,
autocomplete_callback: #autocomplete_callback,
Expand Down Expand Up @@ -164,18 +180,18 @@ pub fn generate_slash_action(inv: &Invocation) -> Result<proc_macro2::TokenStrea
.map(|p| {
let t = &p.type_;
if p.args.flag {
quote::quote! { FLAG }
quote! { FLAG }
} else if let Some(choices) = &p.args.choices {
let choice_indices = (0..choices.0.len()).map(syn::Index::from);
let choice_vals = &choices.0;
quote::quote! { INLINE_CHOICE #t [#(#choice_indices: #choice_vals),*] }
quote! { INLINE_CHOICE #t [#(#choice_indices: #choice_vals),*] }
} else {
quote::quote! { #t }
quote! { #t }
}
})
.collect::<Vec<_>>();

Ok(quote::quote! {
Ok(quote! {
|ctx| Box::pin(async move {
// idk why this can't be put in the macro itself (where the lint is triggered) and
// why clippy doesn't turn off this lint inside macros in the first place
Expand Down Expand Up @@ -216,7 +232,7 @@ pub fn generate_context_menu_action(
}
};

Ok(quote::quote! {
Ok(quote! {
<#param_type as ::poise::ContextMenuParameter<_, _>>::to_action(|ctx, value| {
Box::pin(async move {
let is_framework_cooldown = !ctx.command.manual_cooldowns
Expand Down
56 changes: 55 additions & 1 deletion src/structs/slash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,55 @@ impl<U, E> Clone for ContextMenuCommandAction<U, E> {
}
}

/// An enum to hold the different limits a CommandParameter may have.
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum ValueLimits {
/// Used if the CommandParameter is a string.
Length {
/// See [`serenity::CreateCommandOption::min_length`]
min: Option<u16>,
/// See [`serenity::CreateCommandOption::max_length`]
max: Option<u16>,
},
/// Used if the CommandParameter is an integer or number.
Value {
/// See [`serenity::CreateCommandOption::min_number_value`]
min: Option<f64>,
/// See [`serenity::CreateCommandOption::max_number_value`]
max: Option<f64>,
},
}

impl ValueLimits {
/// Applies the limits to a [`serenity::CreateCommandOption`].
pub fn apply_to_slash_command_option(
self,
mut builder: serenity::CreateCommandOption,
) -> serenity::CreateCommandOption {
match self {
ValueLimits::Length { min, max } => {
if let Some(min) = min {
builder = builder.min_length(min);
}
if let Some(max) = max {
builder = builder.max_length(max);
}
}
ValueLimits::Value { min, max } => {
if let Some(min) = min {
builder = builder.min_number_value(min);
}
if let Some(max) = max {
builder = builder.max_number_value(max);
}
}
}

builder
}
}

/// A single drop-down choice in a slash command choice parameter
#[derive(Debug, Clone)]
pub struct CommandParameterChoice {
Expand Down Expand Up @@ -140,13 +189,15 @@ pub struct CommandParameter<U, E> {
pub channel_types: Option<CowVec<serenity::ChannelType>>,
/// If this parameter is a choice parameter, this is the fixed list of options
pub choices: CowVec<CommandParameterChoice>,
/// For String or Number argument types, this contains the limits.
pub value_limits: Option<ValueLimits>,
/// Closure that sets this parameter's type and min/max value in the given builder
///
/// For example a u32 [`CommandParameter`] would store this as the [`Self::type_setter`]:
/// ```rust
/// # use poise::serenity_prelude as serenity;
/// # let _: fn(serenity::CreateCommandOption) -> serenity::CreateCommandOption =
/// |b| b.kind(serenity::CommandOptionType::Integer).min_int_value(0).max_int_value(u64::MAX)
/// |b| b.kind(serenity::CommandOptionType::Integer)
/// # ;
/// ```
#[derivative(Debug = "ignore")]
Expand Down Expand Up @@ -203,6 +254,9 @@ impl<U, E> CommandParameter<U, E> {
.map(|(name, description)| (name.as_ref(), description.as_ref())),
);
}
if let Some(value_limits) = self.value_limits {
builder = value_limits.apply_to_slash_command_option(builder)
}

Some((self.type_setter?)(builder))
}
Expand Down

0 comments on commit 101bc40

Please sign in to comment.