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 1, 2024
1 parent 96cff2c commit f40f3de
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 44 deletions.
103 changes: 60 additions & 43 deletions macros/src/command/slash.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,45 @@
use super::Invocation;
use quote::{format_ident, quote};
use syn::spanned::Spanned as _;

use super::{Invocation, ParamArgs};
use crate::util::{
extract_type_parameter, iter_tuple_2_to_hash_map, tuple_2_iter_deref, wrap_option_to_string,
extract_type_parameter, iter_tuple_2_to_hash_map, tuple_2_iter_deref, wrap_option,
wrap_option_to_string,
};
use quote::format_ident;
use syn::spanned::Spanned as _;

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();
Expand Down Expand Up @@ -32,81 +68,62 @@ 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 = match inv.args.slash_command {
true => {
if let Some(choices) = &param.args.choices {
let choices = &choices.0;
quote::quote! { vec![#( ::poise::CommandParameterChoice {
quote! { vec![#( ::poise::CommandParameterChoice {
name: ToString::to_string(&#choices),
localizations: Default::default(),
__non_exhaustive: (),
} ),*] }
} else {
quote::quote! { poise::slash_argument_choices!(#type_) }
quote! { poise::slash_argument_choices!(#type_) }
}
}
false => quote::quote! { vec![] },
false => quote! { vec![] },
};

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(
vec![ #( poise::serenity_prelude::ChannelType::#channel_types ),* ]
) },
None => quote::quote! { None },
None => quote! { None },
};

parameter_structs.push((
quote::quote! {
quote! {
::poise::CommandParameter {
name: #param_name.to_string(),
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 @@ -148,18 +165,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 @@ -197,7 +214,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 {
if !ctx.framework.options.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 @@ -107,6 +107,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 @@ -138,13 +187,15 @@ pub struct CommandParameter<U, E> {
pub channel_types: Option<Vec<serenity::ChannelType>>,
/// If this parameter is a choice parameter, this is the fixed list of options
pub choices: Vec<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 @@ -195,6 +246,9 @@ impl<U, E> CommandParameter<U, E> {
builder =
builder.add_int_choice_localized(&choice.name, i as _, choice.localizations.iter());
}
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 f40f3de

Please sign in to comment.