Improve simultaneous choices and autocomplete handling#342
Improve simultaneous choices and autocomplete handling#342
Conversation
|
The documentation preview is available at https://preview.netcord.dev/342. |
There was a problem hiding this comment.
Pull request overview
This PR addresses issue #341 where an explicitly configured autocomplete provider on an enum slash-command parameter was never invoked because enum parameters defaulted to a built-in choices provider. The update adjusts parameter/provider resolution to ensure autocomplete can be used for enums and enforces the Discord constraint that choices and autocomplete cannot be enabled simultaneously.
Changes:
- Refactors
SlashCommandParameterinitialization to prioritize explicitly specifiedAutocompleteProviderTypeover default enum choices handling. - Adds a guard that throws when both a choices provider and an autocomplete provider are specified for the same parameter.
- Adds a test/repro module demonstrating enum autocomplete usage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| Tests/NetCord.Test/ApplicationCommands/Commands.cs | Adds a repro command/module with an enum parameter using AutocompleteProviderType. |
| NetCord.Services/ApplicationCommands/SlashCommandParameter.cs | Refactors provider-selection logic to avoid “choices + autocomplete” conflicts and to allow enum autocomplete. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (slashCommandParameterAttribute?.ChoicesProviderType is { } choicesProviderType) | ||
| { | ||
| var slashCommandParameterAttribute = (SlashCommandParameterAttribute)slashCommandParameterAttributes[0]; | ||
| (TypeReader, NonNullableType, DefaultValue) = ParametersHelper.GetParameterInfo<TContext, ISlashCommandTypeReader, SlashCommandTypeReader<TContext>>(type, parameter, slashCommandParameterAttribute.TypeReaderType, configuration.TypeReaders, configuration.EnumTypeReader); | ||
|
|
||
| var name = Name = slashCommandParameterAttribute.Name ?? configuration.ParameterNameProcessor.ProcessParameterName(parameter.Name!, configuration); | ||
| Description = slashCommandParameterAttribute.Description ?? string.Format(configuration.DefaultParameterDescriptionFormat, name); | ||
|
|
||
| LocalizationPath = path.Add(new SlashCommandParameterLocalizationPathSegment(name)); | ||
|
|
||
| var choicesProviderType = slashCommandParameterAttribute.ChoicesProviderType; | ||
| if (choicesProviderType is null) | ||
| ChoicesProvider = TypeReader.ChoicesProvider; | ||
| else | ||
| { | ||
| if (!choicesProviderType.IsAssignableTo(typeof(IChoicesProvider<TContext>))) | ||
| throw new InvalidOperationException($"'{choicesProviderType}' is not assignable to '{nameof(IChoicesProvider<>)}<{typeof(TContext).Name}>'."); | ||
| ChoicesProvider = (IChoicesProvider<TContext>)Activator.CreateInstance(choicesProviderType)!; | ||
| } | ||
|
|
||
| AutocompleteProviderType = slashCommandParameterAttribute.AutocompleteProviderType ?? TypeReader.AutocompleteProviderType; | ||
| AllowedChannelTypes = slashCommandParameterAttribute.AllowedChannelTypes ?? TypeReader.AllowedChannelTypes; | ||
| MaxValue = slashCommandParameterAttribute._maxValue ?? TypeReader.GetMaxValue(this, configuration); | ||
| MinValue = slashCommandParameterAttribute._minValue ?? TypeReader.GetMinValue(this, configuration); | ||
| MaxLength = slashCommandParameterAttribute._maxLength ?? TypeReader.GetMaxLength(this, configuration); | ||
| MinLength = slashCommandParameterAttribute._minLength ?? TypeReader.GetMinLength(this, configuration); | ||
| if (slashCommandParameterAttribute.AutocompleteProviderType is not null) | ||
| ThrowBothProvidersSpecifiedException(method); | ||
|
|
||
| if (!typeof(IChoicesProvider<TContext>).IsAssignableFrom(choicesProviderType)) | ||
| throw new InvalidDefinitionException($"'{choicesProviderType}' is not assignable to '{typeof(IChoicesProvider<TContext>)}'.", method); | ||
|
|
||
| ChoicesProvider = (IChoicesProvider<TContext>)Activator.CreateInstance(choicesProviderType)!; | ||
| } | ||
| else | ||
| else if (slashCommandParameterAttribute?.AutocompleteProviderType is { } autocompleteProviderType) | ||
| AutocompleteProviderType = autocompleteProviderType; | ||
| else if (typeReader.ChoicesProvider is { } choicesProvider) | ||
| { | ||
| (TypeReader, NonNullableType, DefaultValue) = ParametersHelper.GetParameterInfo<TContext, ISlashCommandTypeReader, SlashCommandTypeReader<TContext>>(type, parameter, null, configuration.TypeReaders, configuration.EnumTypeReader); | ||
|
|
||
| var name = Name = configuration.ParameterNameProcessor.ProcessParameterName(parameter.Name!, configuration); | ||
| LocalizationPath = path.Add(new SlashCommandParameterLocalizationPathSegment(name)); | ||
| Description = string.Format(configuration.DefaultParameterDescriptionFormat, name); | ||
| ChoicesProvider = TypeReader.ChoicesProvider; | ||
| AutocompleteProviderType = TypeReader.AutocompleteProviderType; | ||
| AllowedChannelTypes = TypeReader.AllowedChannelTypes; | ||
| MaxValue = TypeReader.GetMaxValue(this, configuration); | ||
| MinValue = TypeReader.GetMinValue(this, configuration); | ||
| MaxLength = TypeReader.GetMaxLength(this, configuration); | ||
| MinLength = TypeReader.GetMinLength(this, configuration); | ||
| if (typeReader.AutocompleteProviderType is not null) | ||
| ThrowBothProvidersSpecifiedException(method); | ||
|
|
||
| ChoicesProvider = choicesProvider; | ||
| } | ||
| else | ||
| AutocompleteProviderType = typeReader.AutocompleteProviderType; | ||
|
|
|
Lmk when this is ready for merge. |
|
I see that there is a new test failure now. |
Yeah, I was moving code from my laptop. I need to make a big refactor to make it pass. Basically they fail because I throw |
Fixes #341