Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Conversation

OoLunar
Copy link
Contributor

@OoLunar OoLunar commented Aug 18, 2024

Summary

When writing up #2042, @quinchs had helpfully pointed out that the event args generic constraint is not required at all for invoking argument converters - instead the relevant data should be extracted to the most specific converter context type and passed to the ConvertAsync delegate through abstract classes - very similar to how we currently do CommandContexts. This PR grew significantly larger than intended as it involved cleaning up the base command processor, extracting logic to other classes or merging duplicate code into virtual methods. Here's a generalized changelog of what's been done so far:

  • Turns DiscordInteractionData.Options from an IEnumerable to a IReadOnlyList.
  • Removes the DiscordEventArgs generic constraint from BaseCommandProcessor<TEventArgs, TConverter, TConverterContext, TCommandContext>, completely removing the dependency on event arguments.
  • Converter delegate creation has been extracted to BaseCommandProcessor.ConverterDelegateFactory.cs.
  • BaseCommandProcessor now virtually handles argument parsing, default values, params and other argument parsing related logic. Closes Issue with Slash Commands not Delimiting multiple entries in Discord #1954.
  • Introduce InteractionNamingPolicy for interaction command names and parameters. By default, SnakeCaseNamingPolicy is used, however KebebCaseNamingPolicy and LowerCaseNamingPolicy are available. Additionally the user can implement their own naming policy if they desire. Closes InteractionLocalizerAttribute does not snake-case slash command names and args #1950. Removes SnakeCasedNameAttribute.
  • Extract interaction command registration logic to it's own file. Closes Localization doesn't contains a check for max. command description characters #2066. Closes Command failed to register properly when using InteractionLocalizer #2056.
  • Message and User command processors now correctly use logging delegates instead of string extension methods.
  • Params is now replaced with MultiArgumentAttribute. By default, the CommandParameterBuilder will add the MultiArgumentAttribute to the parameter when params is found. int.MaxValue is used to differentiate between params and explicitly defined MAA's. Attempts to correctly spread out interaction options when multiple multi-argument parameters are present, considering min/max values.
  • TextMessageReplyAttribute is now implicitly supported by most argument converters.
  • Verbosely comments all processor logic, hopefully preventing blind decisions or misinformed from being made.
  • Renames local variables to either be more descriptive or consistent with other logic.
  • Fixes enums not be supported by params.
  • Adds an autocomplete provider for enums with more than 25 options.
  • Exponentially optimizes enum argument conversion.
  • Vastly improves logging for autocomplete and parameter choices.
  • Changes the method types for autocomplete and choice providers to return a list of the requested objects directly instead of a dictionary.
  • New IArgumentConverter.GetConverterFriendlyBaseType method, which is required for all argument conversion methods.
  • Isolated processor logic from the core extension logic. The core extension should have zero knowledge of command processors, except for the default processors configuration option. I will likely extract processors to their own packages to reinforce this ideology in the future.
  • New ConfigureCommand event, allowing for mass mutations on command builders and command parameters before they're verified and built. This is non-negotiable.
  • Shadow InteractionConverterContext.Argument so ConverterContext.Argument can still be the raw value.
  • Log when a different argument converter is attempted to be registered for an already registered argument converter.

Notes

Tested. Only accepting feedback regarding grand design or possible functional bugs. Other feedback regarding docs, formatting or nits will be discarded - you have push access to this branch and I'd expect those final changes to be made before merging, not during active development.

Generated Changelog

This changelog does not account for renames and does include internal/private members.

New Types:

  • MultiArgumentAttribute
  • ArgumentFailedConversionResult
  • ArgumentNotParsedResult
  • ConfigureCommandsEventArgs
  • ConverterDelegateFactory
  • MessageCommandLogging
  • EnumAutoCompleteProvider
  • EnumOptionChoiceProvider
  • IInteractionNamingPolicy
  • KebabCaseNamingPolicy
  • LowercaseNamingPolicy
  • SnakeCaseNamingPolicy
  • UserCommandLogging

Removed Types:

  • SnakeCasedNameAttribute
  • ConverterSentinel
  • LazyConverter
  • EnumOptionProvider

Added Methods:

ValueTask CommandsExtension.BuildCommandsAsync();
Regex DiscordChannelConverter.GetChannelMatchingRegex();
ConverterDelegate ConverterDelegateFactory.CreateConverterDelegate();
void BaseCommandProcessor.AddEnumConverters();
ValueTask<IReadOnlyDictionary<CommandParameter, object?>> BaseCommandProcessor.ParseParametersAsync(TConverterContext converterContext);
ValueTask<object?> BaseCommandProcessor.ParseParameterAsync(TConverterContext converterContext);
string IInteractionNamingPolicy.GetCommandName(Command command);
string IInteractionNamingPolicy.GetParameterName(CommandParameter parameter, int arrayIndex);
StringBuilder IInteractionNamingPolicy.TransformText(ReadOnlySpan<char> text);
string KebabCaseNamingPolicy.GetCommandName(Command command);
string KebabCaseNamingPolicy.GetParameterName(CommandParameter parameter, int arrayIndex);
StringBuilder KebabCaseNamingPolicy.TransformText(ReadOnlySpan<char> text);
string LowercaseNamingPolicy.GetCommandName(Command command);
string LowercaseNamingPolicy.GetParameterName(CommandParameter parameter, int arrayIndex);
StringBuilder LowercaseNamingPolicy.TransformText(ReadOnlySpan<char> text);
string SnakeCaseNamingPolicy.GetCommandName(Command command);
string SnakeCaseNamingPolicy.GetParameterName(CommandParameter parameter, int arrayIndex);
StringBuilder SnakeCaseNamingPolicy.TransformText(ReadOnlySpan<char> text);
ValueTask SlashCommandProcessor.PopulateMultiArgumentParametersAsync(Command command, List<DiscordApplicationCommandOption> options);
Task SlashCommandProcessor.ConfigureCommands(CommandsExtension extension, ConfigureCommandsEventArgs eventArgs);
void TextConverterContext.SwitchToMessage(bool value);

Removed Methods:

void CommandsExtension.BuildCommands();
Regex DiscordChannelConverter.getChannelRegex();
Regex DiscordThreadChannelConverter.getChannelRegex();
ValueTask<TCommandContext?> BaseCommandProcessor.ParseArgumentsAsync(TConverterContext converterContext, TEventArgs eventArgs);
string SlashCommandProcessor.ToSnakeCase(ReadOnlySpan<char> str);
ValueTask<SlashCommandContext?> SlashCommandProcessor.ParseArgumentsAsync(InteractionConverterContext converterContext, InteractionCreatedEventArgs eventArgs);
ValueTask<TextCommandContext?> TextCommandProcessor.ParseArgumentsAsync(TextConverterContext converterContext, MessageCreatedEventArgs eventArgs);

Added Properties:

int MultiArgumentAttribute.MaximumArgumentCount { get; init; }
int MultiArgumentAttribute.MinimumArgumentCount { get; init; }
Exception? ArgumentFailedConversionResult.Error { get; init; }
List<CommandBuilder> ConfigureCommandsEventArgs.CommandTrees { get; init; }
BaseCommandProcessor<TConverter, TConverterContext, TCommandContext> ConverterDelegateFactory.CommandProcessor { get; init; }
IInteractionNamingPolicy InteractionConverterContext.ParameterNamePolicy { get; init; }
int InteractionConverterContext.ArgumentEnumerableIndex { get; init; }
IInteractionNamingPolicy SlashCommandConfiguration.ParameterNamePolicy { get; init; }
bool TextMessageReplyAttribute.RequiresReply { get; init; }
DiscordMessage TextConverterContext.Message { get; init; }
bool TextConverterContext.IsOnMessageReply { get; init; }

Removed Properties:

ConverterDelegate<TEventArgs>? LazyConverter.ConverterDelegate { get; init; }
CommandParameter AutoCompleteContext.AutoCompleteArgument { get; init; }
IReadOnlyDictionary<Type, DiscordApplicationCommandOptionType> SlashCommandProcessor.TypeMappings { get; init; }
bool TextMessageReplyAttribute.RequireReplies { get; init; }

Added Fields:

AsyncEvent<CommandsExtension, ConfigureCommandsEventArgs> CommandsExtension.configuringCommands;
Type EnumConverter.baseEnumType;
Action<ILogger, string, string, Exception?> BaseCommandLogging.invalidArgumentConverterImplementation;
Action<ILogger, string, string, string, string, Exception?> BaseCommandLogging.invalidEnumConverterImplementation;
Action<ILogger, string, string, string, Exception?> BaseCommandLogging.duplicateArgumentConvertersRegistered;
MethodInfo ConverterDelegateFactory.createConverterDelegateMethod;
ConverterDelegate? ConverterDelegateFactory.converterDelegate;
Dictionary<Type, ConverterDelegateFactory> BaseCommandProcessor.converterFactories;
Action<ILogger, Exception?> MessageCommandLogging.interactionReceivedBeforeConfigured;
Action<ILogger, string, Exception?> MessageCommandLogging.messageCommandCannotHaveSubcommands;
Action<ILogger, string, Exception?> MessageCommandLogging.messageCommandContextParameterType;
Action<ILogger, string, Exception?> MessageCommandLogging.invalidParameterType;
Action<ILogger, int, string, Exception?> MessageCommandLogging.invalidParameterMissingDefaultValue;
DiscordAutoCompleteChoice[] EnumAutoCompleteProvider.choices;
bool SlashCommandProcessor.isApplicationCommandsRegistered;
Action<ILogger, Exception?> SlashLogging.interactionReceivedBeforeConfigured;
string TextConverterContext.rawArguments;
DiscordMessage TextConverterContext.message;
TextMessageReplyAttribute? TextConverterContext.replyAttribute;
TextConverterContext? TextConverterContext.replyConverterContext;
Action<ILogger, DiscordIntents, Exception?> TextLogging.missingRequiredIntents;
Action<ILogger, Exception?> TextLogging.missingMessageContentIntent;
Action<ILogger, Exception?> UserCommandLogging.interactionReceivedBeforeConfigured;
Action<ILogger, string, Exception?> UserCommandLogging.userCommandCannotHaveSubcommands;
Action<ILogger, string, Exception?> UserCommandLogging.userCommandContextParameterType;
Action<ILogger, string, Exception?> UserCommandLogging.invalidParameterType;
Action<ILogger, int, string, Exception?> UserCommandLogging.invalidParameterMissingDefaultValue;

Removed Fields:

Action<ILogger, string, string, Exception?> BaseCommandLogging.InvalidArgumentConverterImplementation;
Action<ILogger, string, string, string, Exception?> BaseCommandLogging.DuplicateArgumentConvertersRegistered;
Dictionary<Type, LazyConverter> BaseCommandProcessor.lazyConverters;
bool SlashCommandProcessor.configured;
bool SlashCommandProcessor.registered;
bool TextCommandProcessor.configured;
Action<ILogger, DiscordIntents, Exception?> TextLogging.MissingRequiredIntents;
Action<ILogger, Exception?> TextLogging.MissingMessageContentIntent;

Added Events:

  • CommandsExtension.ConfiguringCommands

@OoLunar OoLunar added bugfix enhancement big-change commands For issues related to DSharpPlus.Commands labels Aug 18, 2024
@OoLunar OoLunar added this to the v5.0 milestone Aug 18, 2024
@OoLunar OoLunar self-assigned this Aug 18, 2024
@OoLunar OoLunar force-pushed the oolunar/commands/converter-cleanup branch from 30cfcd8 to 7aeef2a Compare September 25, 2024 05:07
@OoLunar OoLunar marked this pull request as ready for review September 27, 2024 04:51
Copy link
Member

@VelvetToroyashi VelvetToroyashi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At an absolute bare glance, there isn't much that looks off - I do have questions and concerns, but with the vague notion of "only large design issues" wishing to be addressed, I've commented on anything abhorrently problematic.

Copy link
Member

@akiraveliara akiraveliara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some functional issues to fix

re: formatting, that isn't how contributing to a project works at all, and while your IDE breaking is somewhat of an argument i do have grievances with going out and making the formatting worse to then tell other people to go and fix that. i do not think that's okay.

re pooling multi-argument parameter arrays, i took a brief look at that and it's not really worth it, these arrays are relatively small - it could be worth it for arrays of very large flat types (like making an Int512 type in flat memory), but for reference-type and primitive arrays it's not usually worth it, particularly not for slash commands

@OoLunar OoLunar dismissed akiraveliara’s stale review October 3, 2024 00:04

Checking to see if I can rebase the PR or not. Rerequesting in a moment.

@OoLunar OoLunar force-pushed the oolunar/commands/converter-cleanup branch 3 times, most recently from ac48e92 to fe3e25e Compare October 3, 2024 04:18
…t static implementation on every command processor
- Finish moving `GetConverterFriendlyBaseType`
- Prevent `AddConverter` from throwing when the same converter is attempting to be added again
- Early return on `ParseParametersAsync` when no parameters need to be parsed
- Move default value logic from `ParseParametersAsync` to `ParseParameterAsync`
- `ParseParameterAsync` now provides the value that failed to parse when returning `ArgumentFailedConversionValue`
- `ExecuteConverterAsync<T>` now handles multi-argument parameters
@akiraveliara
Copy link
Member

consider "variadic parameters" instead of "var-arg parameters" in docs - that's actually english :P

@akiraveliara akiraveliara mentioned this pull request Oct 4, 2024
36 tasks
docs/articles/commands/var_args_parameters.md Show resolved Hide resolved
docs/articles/toc.yml Outdated Show resolved Hide resolved
docs/articles/toc.yml Outdated Show resolved Hide resolved
@OoLunar OoLunar requested a review from akiraveliara October 6, 2024 04:58
docs/articles/toc.yml Outdated Show resolved Hide resolved
@akiraveliara akiraveliara merged commit b242007 into master Oct 6, 2024
1 check passed
@OoLunar OoLunar deleted the oolunar/commands/converter-cleanup branch October 7, 2024 17:04
Instellate pushed a commit that referenced this pull request Nov 4, 2024
* Oops! I sneezed.

* One of you hooligans needs to fix your IDE formatting. `dotnet format`

* Fix context menu processors requiring exact types instead of ish types

* ConfigureCommands event from #1680

* Documentation; Changed equality implementation on `ConverterDelegateFactory`; Fixed runtime startup exceptions

* Move `GetConverterFriendlyBaseType` to `IArgumentConverter` to prevent static implementation on every command processor

* Several bug fixes to argument parsing and conversion logic

- Finish moving `GetConverterFriendlyBaseType`
- Prevent `AddConverter` from throwing when the same converter is attempting to be added again
- Early return on `ParseParametersAsync` when no parameters need to be parsed
- Move default value logic from `ParseParametersAsync` to `ParseParameterAsync`
- `ParseParameterAsync` now provides the value that failed to parse when returning `ArgumentFailedConversionValue`
- `ExecuteConverterAsync<T>` now handles multi-argument parameters

* Shadow `InteractionConverterContext.Argument` so `ConverterContext.Argument` can still be the raw value

* Fix enum parsing for text commands, which now properly supports boxing (is it called boxing?)

* There can only be one reply to a Discord message

* Defer multi-argument parameter parsing to the base implementation

* Theoretically add support for `TextMessageReplyAttribute` to most argument converters

* Use `IArgumentConverter.GetConverterFriendlyBaseType`

* Configure commands event args. This is non-negotiable.

* Use `IArgumentConverter.GetConverterFriendlyBaseType]`; Defer multi-argument parameter parsing to the base implementation

* `IArgumentConverter.GetConverterFriendlyBaseType`; Theoretically better support multi-argument parameters with slash registration

* Update deps

* Resolve 1 nullable warning

* Fix logic within user command registration, no longer allowing it to be forever false

* Remove old event registration logic

* Expose `IServiceProvider` instead of pointlessly hiding it

* Switch from `AsyncEventArgs` to `DiscordEventArgs`

* Move converter result values to their own namespace instead of repeatedly redeclaring on generic types

* Remove ConverterFriendlySearchFlags; Add new `EnumConverter<T>`; Use the generic version of EnumConverter for parameters.

* Fix compiler errors

* Fix params registering all parameters as required

* Fix choice providers not registering; Correctly isolated slash command logic away from the command parameter builder; Add an autocomplete provider for enums with more than 25 fields

* Remove object constructors and replace them with more type-specific constructors for compile-time safety.

* Vast improvements to the autocomplete and choice providers API

- `SlashAutoCompleteProviderAttribute.AutoCompleteAsync` and `SlashChoiceProviderAttribute.GrabChoicesAsync` will now truncate when more than 25 elements are provided.
  - When options are truncated, it will log which source gave too many options, which it previous didn't.
  - When the type cannot be created, it will log which type failed along with the exception.
  - Try to use the numeric values for enums when possible, serializing only to `string` when the base type is `ulong`.
  - Support double and float enums, because apparently that's a thing?
  - Change the method signatures to return an enumerable of the object directly instead of dictionaries
- Remove `SnakeCasedNameAttribute` in it's entirety, replacing it with `IParameterNamingPolicy`.

* Fix compiler error

* Expose the conversion result to `ArgumentParseException`; Documentation on related members.

* Missed a spot

* Fields to properties

* Correctly apply limit

* Just git- I mean get

* Line feeds?

* `git diff --name-only --diff-filter=AM master | grep ".cs" | xargs -I{line} echo \'{line}\' | xargs dotnet csharpier`

* Update deps

* Add generic overloads

* Format code in docs

* Grammar; New empty `T : new()` generic restraint on `AddConverter<T>()`

* Update autocomplete/choice provider documentation

* Update the missing slash commands section

* Rename `DSharpPlus.Commands.Processors.SlashCommands.ParameterNamingPolicies` to `DSharpPlus.Commands.Processors.SlashCommands.NamingPolicies`

* New `Naming Policies` article

* Document multi-argument parameters

* Don't throw when enums fail to convert

* Support all time units from nanosecond to years

* Give converters their own section; Briefly go over built-in argument converters and their quirks

* Add a guide for invoking argument converters manually

* Enforce max argument count in MAPs

* Undo formatting from `dotnet csharpier`

The new formatting now follows two very simply rules:
- If it does not go beyond the end of my 1080p screen with the file bar open, it stays inlined.
- If Roslyn didn't yell at me about it.

* Allow 3rd party command processors to implement their own generic enum converter

* Begone MAPs, welcome in VAPs

* Remove raw numbers :(

* Footnote

* Formatting

* #1362 was never merged

* Throw away the rest of the MAPs

* docs

* Interface checks

* Variadic

* Attempt to resolve #1950

* Rename `ParameterNamingPolicy` to `NamingPolicy`; Fix inverted if statement

* Use `GetCultureInfo`

* More docs

* Localizing interactions

* Double space

* Fix final GitHub review
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Morty Proxy This is a proxified and sanitized view of the page, visit original site.