From f40f3de1c9ab649ac18e96cea855d28743b0b542 Mon Sep 17 00:00:00 2001 From: GnomedDev Date: Fri, 1 Nov 2024 00:20:27 +0000 Subject: [PATCH] Move value limitations away from type_setter --- macros/src/command/slash.rs | 103 +++++++++++++++++++++--------------- src/structs/slash.rs | 56 +++++++++++++++++++- 2 files changed, 115 insertions(+), 44 deletions(-) diff --git a/macros/src/command/slash.rs b/macros/src/command/slash.rs index a380da024dc..5e182114e2e 100644 --- a/macros/src/command/slash.rs +++ b/macros/src/command/slash.rs @@ -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> { + 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, syn::Error> { let mut parameter_structs = Vec::new(); @@ -32,74 +68,54 @@ pub fn generate_parameters(inv: &Invocation) -> Result { - 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 ¶m.args.min { - Some(x) => quote::quote! { .min_number_value(#x as f64) }, - None => quote::quote! {}, - }; - let max_value_setter = match ¶m.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 ¶m.args.min_length { - Some(x) => quote::quote! { .min_length(#x) }, - None => quote::quote! {}, - }; - let max_length_setter = match ¶m.args.max_length { - Some(x) => quote::quote! { .max_length(#x) }, - None => quote::quote! {}, - }; + let value_limits = wrap_option(generate_value_limits(¶m.args, param.span)?); + let type_setter = match inv.args.slash_command { true => { if let Some(_choices) = ¶m.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) = ¶m.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 ¶m.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, @@ -107,6 +123,7 @@ pub fn generate_parameters(inv: &Invocation) -> Result Result>(); - 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 @@ -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 { diff --git a/src/structs/slash.rs b/src/structs/slash.rs index ebbe2a7a9b7..70002fbf71e 100644 --- a/src/structs/slash.rs +++ b/src/structs/slash.rs @@ -107,6 +107,55 @@ impl Clone for ContextMenuCommandAction { } } +/// 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, + /// See [`serenity::CreateCommandOption::max_length`] + max: Option, + }, + /// Used if the CommandParameter is an integer or number. + Value { + /// See [`serenity::CreateCommandOption::min_number_value`] + min: Option, + /// See [`serenity::CreateCommandOption::max_number_value`] + max: Option, + }, +} + +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 { @@ -138,13 +187,15 @@ pub struct CommandParameter { pub channel_types: Option>, /// If this parameter is a choice parameter, this is the fixed list of options pub choices: Vec, + /// For String or Number argument types, this contains the limits. + pub value_limits: Option, /// 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")] @@ -195,6 +246,9 @@ impl CommandParameter { 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)) }