From 694eff54ec87197cf0432566712280957c2e96e8 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sun, 23 Apr 2017 21:30:39 +0100 Subject: [PATCH] Rewrite command builders using C#7 features Not completely tested as of yet --- .../Attributes/PreconditionAttribute.cs | 2 +- .../RequireBotPermissionAttribute.cs | 2 +- .../Preconditions/RequireContextAttribute.cs | 2 +- .../Preconditions/RequireNsfwAttribute.cs | 2 +- .../Preconditions/RequireOwnerAttribute.cs | 2 +- .../RequireUserPermissionAttribute.cs | 2 +- .../Builders/CommandBuilder.cs | 75 ++----- .../Builders/ModuleBuilder.cs | 4 +- .../Builders/ModuleClassBuilder.cs | 119 ++++++---- .../Builders/OverloadBuilder.cs | 91 ++++++++ .../Builders/ParameterBuilder.cs | 12 +- src/Discord.Net.Commands/CommandException.cs | 8 +- src/Discord.Net.Commands/CommandMatch.cs | 9 - src/Discord.Net.Commands/CommandParser.cs | 10 +- src/Discord.Net.Commands/CommandService.cs | 93 +++++--- src/Discord.Net.Commands/Info/CommandInfo.cs | 184 +--------------- src/Discord.Net.Commands/Info/OverloadInfo.cs | 207 ++++++++++++++++++ .../Info/ParameterInfo.cs | 6 +- src/Discord.Net.Commands/PrimitiveParsers.cs | 5 - .../Readers/PrimitiveTypeReader.cs | 10 +- .../Results/ParseResult.cs | 21 +- 21 files changed, 509 insertions(+), 357 deletions(-) create mode 100644 src/Discord.Net.Commands/Builders/OverloadBuilder.cs create mode 100644 src/Discord.Net.Commands/Info/OverloadInfo.cs diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index e099380f6..98551ebfc 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -6,6 +6,6 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public abstract class PreconditionAttribute : Attribute { - public abstract Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services); + public abstract Task CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs index 82975a2f6..018bb03ff 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -42,7 +42,7 @@ namespace Discord.Commands GuildPermission = null; } - public override async Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override async Task CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services) { var guildUser = await context.Guild.GetCurrentUserAsync(); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index a221eb4a9..827da6fb0 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -38,7 +38,7 @@ namespace Discord.Commands Contexts = contexts; } - public override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override Task CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services) { bool isValid = false; diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs index 94235b1ae..f4149210a 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs @@ -9,7 +9,7 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireNsfwAttribute : PreconditionAttribute { - public override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override Task CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services) { if (context.Channel.IsNsfw) return Task.FromResult(PreconditionResult.FromSuccess()); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs index 0852ce39c..deb745803 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -11,7 +11,7 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireOwnerAttribute : PreconditionAttribute { - public override async Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override async Task CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services) { switch (context.Client.TokenType) { diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index 44c69d76a..1dbea3b9e 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -43,7 +43,7 @@ namespace Discord.Commands GuildPermission = null; } - public override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override Task CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services) { var guildUser = context.User as IGuildUser; diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index ff89b7559..a4f232fc6 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -8,22 +8,17 @@ namespace Discord.Commands.Builders { public class CommandBuilder { - private readonly List _preconditions; - private readonly List _parameters; + private readonly List _overloads; private readonly List _aliases; public ModuleBuilder Module { get; } - internal Func Callback { get; set; } public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } public string PrimaryAlias { get; set; } - public RunMode RunMode { get; set; } - public int Priority { get; set; } - public IReadOnlyList Preconditions => _preconditions; - public IReadOnlyList Parameters => _parameters; + public IReadOnlyList Overloads => _overloads; public IReadOnlyList Aliases => _aliases; //Automatic @@ -31,20 +26,23 @@ namespace Discord.Commands.Builders { Module = module; - _preconditions = new List(); - _parameters = new List(); + _overloads = new List(); _aliases = new List(); } //User-defined - internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func callback) + internal CommandBuilder(ModuleBuilder module, string primaryAlias, Action defaultOverloadBuilder) : this(module) { Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); - Discord.Preconditions.NotNull(callback, nameof(callback)); + Discord.Preconditions.NotNull(defaultOverloadBuilder, nameof(defaultOverloadBuilder)); - Callback = callback; PrimaryAlias = primaryAlias; _aliases.Add(primaryAlias); + + var defaultOverload = new OverloadBuilder(this); + defaultOverloadBuilder(defaultOverload); + + _overloads.Add(defaultOverload); } public CommandBuilder WithName(string name) @@ -62,16 +60,6 @@ namespace Discord.Commands.Builders Remarks = remarks; return this; } - public CommandBuilder WithRunMode(RunMode runMode) - { - RunMode = runMode; - return this; - } - public CommandBuilder WithPriority(int priority) - { - Priority = priority; - return this; - } public CommandBuilder AddAliases(params string[] aliases) { @@ -83,30 +71,12 @@ namespace Discord.Commands.Builders } return this; } - public CommandBuilder AddPrecondition(PreconditionAttribute precondition) - { - _preconditions.Add(precondition); - return this; - } - public CommandBuilder AddParameter(string name, Action createFunc) - { - var param = new ParameterBuilder(this, name, typeof(T)); - createFunc(param); - _parameters.Add(param); - return this; - } - public CommandBuilder AddParameter(string name, Type type, Action createFunc) - { - var param = new ParameterBuilder(this, name, type); - createFunc(param); - _parameters.Add(param); - return this; - } - internal CommandBuilder AddParameter(Action createFunc) + + public CommandBuilder AddOverload(Action overloadBuilder) { - var param = new ParameterBuilder(this); - createFunc(param); - _parameters.Add(param); + var overload = new OverloadBuilder(this); + overloadBuilder(overload); + _overloads.Add(overload); return this; } @@ -116,20 +86,7 @@ namespace Discord.Commands.Builders if (Name == null) Name = PrimaryAlias; - if (_parameters.Count > 0) - { - var lastParam = _parameters[_parameters.Count - 1]; - - var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple); - if ((firstMultipleParam != null) && (firstMultipleParam != lastParam)) - throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag."); - - var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder); - if ((firstRemainderParam != null) && (firstRemainderParam != lastParam)) - throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag."); - } - return new CommandInfo(this, info, service); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index d79239057..97a2c220b 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -74,9 +74,9 @@ namespace Discord.Commands.Builders _preconditions.Add(precondition); return this; } - public ModuleBuilder AddCommand(string primaryAlias, Func callback, Action createFunc) + public ModuleBuilder AddCommand(string primaryAlias, Action defaultOverloadBuilder, Action createFunc) { - var builder = new CommandBuilder(this, primaryAlias, callback); + var builder = new CommandBuilder(this, primaryAlias, defaultOverloadBuilder); createFunc(builder); _commands.Add(builder); return this; diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index d8464ea72..9ae78a863 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -113,45 +113,78 @@ namespace Discord.Commands var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); - foreach (var method in validCommands) + var groupedCommands = validCommands.GroupBy(x => x.GetCustomAttribute().Text); + foreach (var overloads in groupedCommands) { builder.AddCommand((command) => { - BuildCommand(command, typeInfo, method, service); + string firstName = null; + + foreach (var method in overloads) + { + if (firstName == null) + firstName = method.Name; + + command.AddOverload((overload) => + { + BuildOverload(overload, typeInfo, method, service); + }); + } + + var allAttributes = overloads.SelectMany(x => x.GetCustomAttributes()); + BuildCommand(command, firstName, allAttributes, service); }); } } - private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service) + private static void BuildCommand(CommandBuilder builder, string defaultName, IEnumerable attributes, CommandService service) { - var attributes = method.GetCustomAttributes(); - foreach (var attribute in attributes) { - // TODO: C#7 type switch - if (attribute is CommandAttribute) + switch (attribute) { - var cmdAttr = attribute as CommandAttribute; - builder.AddAliases(cmdAttr.Text); - builder.RunMode = cmdAttr.RunMode; - builder.Name = builder.Name ?? cmdAttr.Text; + case CommandAttribute command: + builder.AddAliases(command.Text); + builder.Name = builder.Name ?? command.Text; + break; + case NameAttribute name: + builder.Name = name.Text; + break; + case SummaryAttribute summary: + builder.Summary = summary.Text; + break; + case RemarksAttribute remarks: + builder.Remarks = remarks.Text; + break; + case AliasAttribute alias: + builder.AddAliases(alias.Aliases); + break; } - else if (attribute is NameAttribute) - builder.Name = (attribute as NameAttribute).Text; - else if (attribute is PriorityAttribute) - builder.Priority = (attribute as PriorityAttribute).Priority; - else if (attribute is SummaryAttribute) - builder.Summary = (attribute as SummaryAttribute).Text; - else if (attribute is RemarksAttribute) - builder.Remarks = (attribute as RemarksAttribute).Text; - else if (attribute is AliasAttribute) - builder.AddAliases((attribute as AliasAttribute).Aliases); - else if (attribute is PreconditionAttribute) - builder.AddPrecondition(attribute as PreconditionAttribute); } if (builder.Name == null) - builder.Name = method.Name; + builder.Name = defaultName; + } + + private static void BuildOverload(OverloadBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service) + { + var attributes = method.GetCustomAttributes(); + + foreach (var attribute in attributes) + { + switch (attribute) + { + case CommandAttribute command: + builder.RunMode = command.RunMode; + break; + case PriorityAttribute priority: + builder.Priority = priority.Priority; + break; + case PreconditionAttribute precondition: + builder.AddPrecondition(precondition); + break; + } + } var parameters = method.GetParameters(); int pos = 0, count = parameters.Length; @@ -196,23 +229,29 @@ namespace Discord.Commands foreach (var attribute in attributes) { // TODO: C#7 type switch - if (attribute is SummaryAttribute) - builder.Summary = (attribute as SummaryAttribute).Text; - else if (attribute is OverrideTypeReaderAttribute) - builder.TypeReader = GetTypeReader(service, paramType, (attribute as OverrideTypeReaderAttribute).TypeReader); - else if (attribute is ParameterPreconditionAttribute) - builder.AddPrecondition(attribute as ParameterPreconditionAttribute); - else if (attribute is ParamArrayAttribute) - { - builder.IsMultiple = true; - paramType = paramType.GetElementType(); - } - else if (attribute is RemainderAttribute) + switch (attribute) { - if (position != count-1) - throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); - - builder.IsRemainder = true; + case SummaryAttribute summary: + builder.Summary = summary.Text; + break; + case OverrideTypeReaderAttribute typeReader: + builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader); + break; + case ParameterPreconditionAttribute precondition: + builder.AddPrecondition(precondition); + break; + case ParamArrayAttribute paramArray: + if (position != count - 1) + throw new InvalidOperationException("Params parameters must be the last parameter in a command."); + builder.IsMultiple = true; + paramType = paramType.GetElementType(); + break; + case RemainderAttribute remainder: + if (position != count - 1) + throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); + + builder.IsRemainder = true; + break; } } diff --git a/src/Discord.Net.Commands/Builders/OverloadBuilder.cs b/src/Discord.Net.Commands/Builders/OverloadBuilder.cs new file mode 100644 index 000000000..a4301c8a3 --- /dev/null +++ b/src/Discord.Net.Commands/Builders/OverloadBuilder.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Diagnostics; + +using CommandCallback = System.Func; + +namespace Discord.Commands.Builders +{ + public class OverloadBuilder + { + private readonly List _preconditions; + private readonly List _parameters; + + public CommandBuilder Command { get; } + public CommandCallback Callback { get; set; } + + public RunMode RunMode { get; set; } + public int Priority { get; set; } + + public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Parameters => _parameters; + + internal OverloadBuilder(CommandBuilder command) + { + Command = command; + + _preconditions = new List(); + _parameters = new List(); + } + + public OverloadBuilder WithRunMode(RunMode runMode) + { + RunMode = runMode; + return this; + } + + public OverloadBuilder WithPriority(int priority) + { + Priority = priority; + return this; + } + + public OverloadBuilder WithCallback(CommandCallback callback) + { + Callback = callback; + return this; + } + + public OverloadBuilder AddPrecondition(PreconditionAttribute precondition) + { + _preconditions.Add(precondition); + return this; + } + internal OverloadBuilder AddParameter(Action createFunc) + { + var param = new ParameterBuilder(this); + createFunc(param); + _parameters.Add(param); + return this; + } + public OverloadBuilder AddParameter(string name, Type type, Action createFunc) + { + var param = new ParameterBuilder(this, name, type); + createFunc(param); + _parameters.Add(param); + return this; + } + + internal OverloadInfo Build(CommandInfo info, CommandService service) + { + Discord.Preconditions.NotNull(Callback, nameof(Callback)); + + if (_parameters.Count > 0) + { + var lastParam = _parameters[_parameters.Count - 1]; + + var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple); + if ((firstMultipleParam != null) && (firstMultipleParam != lastParam)) + throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag."); + + var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder); + if ((firstRemainderParam != null) && (firstRemainderParam != lastParam)) + throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag."); + } + + return new OverloadInfo(this, info, service); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 6761033b0..82476dd8a 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -11,6 +11,7 @@ namespace Discord.Commands.Builders private readonly List _preconditions; public CommandBuilder Command { get; } + public OverloadBuilder Overload { get; } public string Name { get; internal set; } public Type ParameterType { get; internal set; } @@ -24,15 +25,16 @@ namespace Discord.Commands.Builders public IReadOnlyList Preconditions => _preconditions; //Automatic - internal ParameterBuilder(CommandBuilder command) + internal ParameterBuilder(OverloadBuilder overload) { _preconditions = new List(); - Command = command; + Command = overload.Command; + Overload = overload; } //User-defined - internal ParameterBuilder(CommandBuilder command, string name, Type type) - : this(command) + internal ParameterBuilder(OverloadBuilder overload, string name, Type type) + : this(overload) { Discord.Preconditions.NotNull(name, nameof(name)); @@ -90,7 +92,7 @@ namespace Discord.Commands.Builders return this; } - internal ParameterInfo Build(CommandInfo info) + internal ParameterInfo Build(OverloadInfo info) { if (TypeReader == null) throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified"); diff --git a/src/Discord.Net.Commands/CommandException.cs b/src/Discord.Net.Commands/CommandException.cs index d5300841a..2562eb000 100644 --- a/src/Discord.Net.Commands/CommandException.cs +++ b/src/Discord.Net.Commands/CommandException.cs @@ -5,12 +5,14 @@ namespace Discord.Commands public class CommandException : Exception { public CommandInfo Command { get; } + public OverloadInfo Overload { get; } public ICommandContext Context { get; } - public CommandException(CommandInfo command, ICommandContext context, Exception ex) - : base($"Error occurred executing {command.GetLogText(context)}.", ex) + public CommandException(OverloadInfo overload, ICommandContext context, Exception ex) + : base($"Error occurred executing {overload.GetLogText(context)}.", ex) { - Command = command; + Overload = overload; + Command = overload.Command; Context = context; } } diff --git a/src/Discord.Net.Commands/CommandMatch.cs b/src/Discord.Net.Commands/CommandMatch.cs index 04a2d040f..5a6267138 100644 --- a/src/Discord.Net.Commands/CommandMatch.cs +++ b/src/Discord.Net.Commands/CommandMatch.cs @@ -15,14 +15,5 @@ namespace Discord.Commands Command = command; Alias = alias; } - - public Task CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) - => Command.CheckPreconditionsAsync(context, services); - public Task ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) - => Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult); - public Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) - => Command.ExecuteAsync(context, argList, paramList, services); - public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) - => Command.ExecuteAsync(context, parseResult, services); } } diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 5b4ba2480..1682fce61 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -13,7 +13,7 @@ namespace Discord.Commands QuotedParameter } - public static async Task ParseArgs(CommandInfo command, ICommandContext context, string input, int startPos) + public static async Task ParseArgs(OverloadInfo overload, ICommandContext context, string input, int startPos) { ParameterInfo curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); @@ -66,7 +66,7 @@ namespace Discord.Commands else { if (curParam == null) - curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null; + curParam = overload.Parameters.Count > argList.Count ? overload.Parameters[argList.Count] : null; if (curParam != null && curParam.IsRemainder) { @@ -145,9 +145,9 @@ namespace Discord.Commands return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete"); //Add missing optionals - for (int i = argList.Count; i < command.Parameters.Count; i++) + for (int i = argList.Count; i < overload.Parameters.Count; i++) { - var param = command.Parameters[i]; + var param = overload.Parameters[i]; if (param.IsMultiple) continue; if (!param.IsOptional) @@ -155,7 +155,7 @@ namespace Discord.Commands argList.Add(TypeReaderResult.FromSuccess(param.DefaultValue)); } - return ParseResult.FromSuccess(argList.ToImmutable(), paramList.ToImmutable()); + return ParseResult.FromSuccess(overload, argList.ToImmutable(), paramList.ToImmutable()); } } } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index f526e8f3b..798bc46c1 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -33,7 +33,7 @@ namespace Discord.Commands public IEnumerable Modules => _moduleDefs.Select(x => x); public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); - public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new {y.Key, y.Value})).ToLookup(x => x.Key, x => x.Value); + public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) @@ -59,6 +59,13 @@ namespace Discord.Commands foreach (var type in PrimitiveParsers.SupportedTypes) _defaultTypeReaders[type] = PrimitiveTypeReader.Create(type); + _defaultTypeReaders[typeof(string)] = new PrimitiveTypeReader(0, + (string x, out string y) => + { + y = x; + return true; + }); + var entityTypeReaders = ImmutableList.CreateBuilder>(); entityTypeReaders.Add(new Tuple(typeof(IMessage), typeof(MessageTypeReader<>))); entityTypeReaders.Add(new Tuple(typeof(IChannel), typeof(ChannelTypeReader<>))); @@ -196,7 +203,7 @@ namespace Discord.Commands } public void AddTypeReader(Type type, TypeReader reader) { - var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentDictionary()); + var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary()); readers[reader.GetType()] = reader; } internal IDictionary GetTypeReaders(Type type) @@ -235,13 +242,13 @@ namespace Discord.Commands } //Execution - public SearchResult Search(ICommandContext context, int argPos) + public SearchResult Search(ICommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); public SearchResult Search(ICommandContext context, string input) { string searchInput = _caseSensitive ? input : input.ToLowerInvariant(); - var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray(); - + var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Overloads.Average(y => y.Priority)).ToImmutableArray(); + if (matches.Length > 0) return SearchResult.FromSuccess(input, matches); else @@ -261,41 +268,67 @@ namespace Discord.Commands var commands = searchResult.Commands; for (int i = 0; i < commands.Count; i++) { - var preconditionResult = await commands[i].CheckPreconditionsAsync(context, services).ConfigureAwait(false); - if (!preconditionResult.IsSuccess) + var command = commands[i].Command; + var overloads = command.Overloads.OrderByDescending(x => x.Priority).ToImmutableArray(); + + var preconditionResult = PreconditionResult.FromSuccess(); + for (int j = 0; j < overloads.Length; j++) + { + preconditionResult = await overloads[j].CheckPreconditionsAsync(context, services).ConfigureAwait(false); + if (!preconditionResult.IsSuccess) + { + if (i == commands.Count && j == overloads.Length) + return preconditionResult; + else + continue; + } + } + + var rawParseResults = new List(); + foreach (var overload in overloads) { - if (commands.Count == 1) - return preconditionResult; - else - continue; + rawParseResults.Add(await overload.ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false)); } - var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false); - if (!parseResult.IsSuccess) + //order by average score + var orderedParseResults = rawParseResults.OrderByDescending( + x => !x.IsSuccess ? 0 : + (x.ArgValues.Count > 0 ? x.ArgValues.Average(y => y.Values.Max(z => z.Score)) : 0) + + (x.ParamValues.Count > 0 ? x.ParamValues.Average(y => y.Values.Max(z => z.Score)) : 0)); + + var parseResults = orderedParseResults.ToImmutableArray(); + + for (int j = 0; j < parseResults.Length; j++) { - if (parseResult.Error == CommandError.MultipleMatches) + var parseResult = parseResults[j]; + var overload = parseResult.Overload; + + if (!parseResult.IsSuccess) { - IReadOnlyList argList, paramList; - switch (multiMatchHandling) + if (parseResult.Error == CommandError.MultipleMatches) { - case MultiMatchHandling.Best: - argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - parseResult = ParseResult.FromSuccess(argList, paramList); - break; + IReadOnlyList argList, paramList; + switch (multiMatchHandling) + { + case MultiMatchHandling.Best: + argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + parseResult = ParseResult.FromSuccess(overload, argList, paramList); + break; + } } - } - if (!parseResult.IsSuccess) - { - if (commands.Count == 1) - return parseResult; - else - continue; + if (!parseResult.IsSuccess) + { + if (i == commands.Count && j == parseResults.Length) + return parseResult; + else + continue; + } } - } - return await commands[i].ExecuteAsync(context, parseResult, services).ConfigureAwait(false); + return await overload.ExecuteAsync(context, parseResult, services).ConfigureAwait(false); + } } return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 5acd1f648..01c12d377 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -15,34 +15,22 @@ namespace Discord.Commands [DebuggerDisplay("{Name,nq}")] public class CommandInfo { - private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); - private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); - - private readonly Func _action; - public ModuleInfo Module { get; } public string Name { get; } public string Summary { get; } public string Remarks { get; } - public int Priority { get; } - public bool HasVarArgs { get; } - public RunMode RunMode { get; } public IReadOnlyList Aliases { get; } - public IReadOnlyList Parameters { get; } - public IReadOnlyList Preconditions { get; } + public IReadOnlyList Overloads { get; } internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service) { Module = module; - + Name = builder.Name; Summary = builder.Summary; Remarks = builder.Remarks; - RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode); - Priority = builder.Priority; - Aliases = module.Aliases .Permutate(builder.Aliases, (first, second) => { @@ -56,171 +44,7 @@ namespace Discord.Commands .Select(x => service._caseSensitive ? x : x.ToLowerInvariant()) .ToImmutableArray(); - Preconditions = builder.Preconditions.ToImmutableArray(); - - Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); - HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; - - _action = builder.Callback; - } - - public async Task CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) - { - services = services ?? EmptyServiceProvider.Instance; - - foreach (PreconditionAttribute precondition in Module.Preconditions) - { - var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); - if (!result.IsSuccess) - return result; - } - - foreach (PreconditionAttribute precondition in Preconditions) - { - var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); - if (!result.IsSuccess) - return result; - } - - return PreconditionResult.FromSuccess(); - } - - public async Task ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult? preconditionResult = null) - { - if (!searchResult.IsSuccess) - return ParseResult.FromError(searchResult); - if (preconditionResult != null && !preconditionResult.Value.IsSuccess) - return ParseResult.FromError(preconditionResult.Value); - - string input = searchResult.Text.Substring(startIndex); - return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); - } - - public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) - { - if (!parseResult.IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult)); - - var argList = new object[parseResult.ArgValues.Count]; - for (int i = 0; i < parseResult.ArgValues.Count; i++) - { - if (!parseResult.ArgValues[i].IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i])); - argList[i] = parseResult.ArgValues[i].Values.First().Value; - } - - var paramList = new object[parseResult.ParamValues.Count]; - for (int i = 0; i < parseResult.ParamValues.Count; i++) - { - if (!parseResult.ParamValues[i].IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i])); - paramList[i] = parseResult.ParamValues[i].Values.First().Value; - } - - return ExecuteAsync(context, argList, paramList, services); - } - public async Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) - { - services = services ?? EmptyServiceProvider.Instance; - - try - { - object[] args = GenerateArgs(argList, paramList); - - for (int position = 0; position < Parameters.Count; position++) - { - var parameter = Parameters[position]; - var argument = args[position]; - var result = await parameter.CheckPreconditionsAsync(context, argument, services).ConfigureAwait(false); - if (!result.IsSuccess) - return ExecuteResult.FromError(result); - } - - switch (RunMode) - { - case RunMode.Sync: //Always sync - await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); - break; - case RunMode.Async: //Always async - var t2 = Task.Run(async () => - { - await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); - }); - break; - } - return ExecuteResult.FromSuccess(); - } - catch (Exception ex) - { - return ExecuteResult.FromError(ex); - } - } - - private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider services) - { - await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); - try - { - await _action(context, args, services).ConfigureAwait(false); - } - catch (Exception ex) - { - var originalEx = ex; - while (ex is TargetInvocationException) //Happens with void-returning commands - ex = ex.InnerException; - - var wrappedEx = new CommandException(this, context, ex); - await Module.Service._cmdLogger.ErrorAsync(wrappedEx).ConfigureAwait(false); - if (Module.Service._throwOnError) - { - if (ex == originalEx) - throw; - else - ExceptionDispatchInfo.Capture(ex).Throw(); - } - } - await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); - } - - private object[] GenerateArgs(IEnumerable argList, IEnumerable paramsList) - { - int argCount = Parameters.Count; - var array = new object[Parameters.Count]; - if (HasVarArgs) - argCount--; - - int i = 0; - foreach (var arg in argList) - { - if (i == argCount) - throw new InvalidOperationException("Command was invoked with too many parameters"); - array[i++] = arg; - } - if (i < argCount) - throw new InvalidOperationException("Command was invoked with too few parameters"); - - if (HasVarArgs) - { - var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t => - { - var method = _convertParamsMethod.MakeGenericMethod(t); - return (Func, object>)method.CreateDelegate(typeof(Func, object>)); - }); - array[i] = func(paramsList); - } - - return array; - } - - private static T[] ConvertParamsList(IEnumerable paramsList) - => paramsList.Cast().ToArray(); - - internal string GetLogText(ICommandContext context) - { - if (context.Guild != null) - return $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}"; - else - return $"\"{Name}\" for {context.User} in {context.Channel}"; + Overloads = builder.Overloads.Select(x => x.Build(this, service)).ToImmutableArray(); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Commands/Info/OverloadInfo.cs b/src/Discord.Net.Commands/Info/OverloadInfo.cs new file mode 100644 index 000000000..cd20dda1e --- /dev/null +++ b/src/Discord.Net.Commands/Info/OverloadInfo.cs @@ -0,0 +1,207 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using System.Reflection; +using System.Diagnostics; + +using Discord.Commands.Builders; +using System.Runtime.ExceptionServices; + +namespace Discord.Commands +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class OverloadInfo + { + private static readonly MethodInfo _convertParamsMethod = typeof(OverloadInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); + private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); + + private readonly Func _action; + + public CommandInfo Command { get; } + public int Priority { get; } + public bool HasVarArgs { get; } + public RunMode RunMode { get; } + + public IReadOnlyList Parameters { get; } + public IReadOnlyList Preconditions { get; } + + internal OverloadInfo(OverloadBuilder builder, CommandInfo command, CommandService service) + { + Command = command; + + RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode); + Priority = builder.Priority; + + Preconditions = builder.Preconditions.ToImmutableArray(); + + Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); + HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; + + _action = builder.Callback; + } + + public async Task CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) + { + if (services == null) + services = EmptyServiceProvider.Instance; + + foreach (PreconditionAttribute precondition in Command.Module.Preconditions) + { + var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); + if (!result.IsSuccess) + return result; + } + + foreach (PreconditionAttribute precondition in Preconditions) + { + var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); + if (!result.IsSuccess) + return result; + } + + return PreconditionResult.FromSuccess(); + } + + public async Task ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) + { + if (!searchResult.IsSuccess) + return ParseResult.FromError(searchResult); + if (preconditionResult != null && !preconditionResult.Value.IsSuccess) + return ParseResult.FromError(preconditionResult.Value); + + string input = searchResult.Text; + var matchingAliases = Command.Aliases.Where(alias => input.StartsWith(alias)); + + string matchingAlias = ""; + foreach (string alias in matchingAliases) + { + if (alias.Length > matchingAlias.Length) + matchingAlias = alias; + } + + input = input.Substring(matchingAlias.Length); + + return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); + } + + public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) + { + if (!parseResult.IsSuccess) + return Task.FromResult(ExecuteResult.FromError(parseResult)); + + var argList = new object[parseResult.ArgValues.Count]; + for (int i = 0; i < parseResult.ArgValues.Count; i++) + { + if (!parseResult.ArgValues[i].IsSuccess) + return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i])); + argList[i] = parseResult.ArgValues[i].Values.First().Value; + } + + var paramList = new object[parseResult.ParamValues.Count]; + for (int i = 0; i < parseResult.ParamValues.Count; i++) + { + if (!parseResult.ParamValues[i].IsSuccess) + return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i])); + paramList[i] = parseResult.ParamValues[i].Values.First().Value; + } + + return ExecuteAsync(context, argList, paramList, services); + } + public async Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) + { + if (services == null) + services = EmptyServiceProvider.Instance; + + try + { + var args = GenerateArgs(argList, paramList); + switch (RunMode) + { + case RunMode.Sync: //Always sync + await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); + break; + case RunMode.Async: //Always async + var t2 = Task.Run(() => ExecuteAsyncInternal(context, args, services)); + break; + } + return ExecuteResult.FromSuccess(); + } + catch (Exception ex) + { + return ExecuteResult.FromError(ex); + } + } + + private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider map) + { + await Command.Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); + try + { + await _action(context, args, map).ConfigureAwait(false); + } + catch (Exception ex) + { + var originalEx = ex; + while (ex is TargetInvocationException) //Happens with void-returning commands + ex = ex.InnerException; + + var wrappedEx = new CommandException(this, context, ex); + await Command.Module.Service._cmdLogger.ErrorAsync(wrappedEx).ConfigureAwait(false); + if (Command.Module.Service._throwOnError) + { + if (ex == originalEx) + throw; + else + ExceptionDispatchInfo.Capture(ex).Throw(); + } + } + await Command.Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); + } + + internal string GetLogText(ICommandContext context) + { + if (context.Guild != null) + return $"\"{Command.Name}\" for {context.User} in {context.Guild}/{context.Channel}"; + else + return $"\"{Command.Name}\" for {context.User} in {context.Channel}"; + } + + private object[] GenerateArgs(IEnumerable argList, IEnumerable paramsList) + { + int argCount = Parameters.Count; + var array = new object[Parameters.Count]; + if (HasVarArgs) + argCount--; + + int i = 0; + foreach (var arg in argList) + { + if (i == argCount) + throw new InvalidOperationException("Command was invoked with too many parameters"); + array[i++] = arg; + } + if (i < argCount) + throw new InvalidOperationException("Command was invoked with too few parameters"); + + if (HasVarArgs) + { + var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t => + { + var method = _convertParamsMethod.MakeGenericMethod(t); + return (Func, object>)method.CreateDelegate(typeof(Func, object>)); + }); + array[i] = func(paramsList); + } + + return array; + } + + private string DebuggerDisplay => $"{Command.Name} ({Priority}, {RunMode})"; + + private static T[] ConvertParamsList(IEnumerable paramsList) + => paramsList.Cast().ToArray(); + } +} diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 2ecf26a9f..eb89b2a36 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -12,6 +12,7 @@ namespace Discord.Commands private readonly TypeReader _reader; public CommandInfo Command { get; } + public OverloadInfo Overload { get; } public string Name { get; } public string Summary { get; } public bool IsOptional { get; } @@ -22,9 +23,10 @@ namespace Discord.Commands public IReadOnlyList Preconditions { get; } - internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service) + internal ParameterInfo(ParameterBuilder builder, OverloadInfo overload, CommandService service) { - Command = command; + Command = overload.Command; + Overload = overload; Name = builder.Name; Summary = builder.Summary; diff --git a/src/Discord.Net.Commands/PrimitiveParsers.cs b/src/Discord.Net.Commands/PrimitiveParsers.cs index 623ddafa7..6a54ba402 100644 --- a/src/Discord.Net.Commands/PrimitiveParsers.cs +++ b/src/Discord.Net.Commands/PrimitiveParsers.cs @@ -31,11 +31,6 @@ namespace Discord.Commands parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate)DateTimeOffset.TryParse; parserBuilder[typeof(TimeSpan)] = (TryParseDelegate)TimeSpan.TryParse; parserBuilder[typeof(char)] = (TryParseDelegate)char.TryParse; - parserBuilder[typeof(string)] = (TryParseDelegate)delegate (string str, out string value) - { - value = str; - return true; - }; return parserBuilder.ToImmutable(); } diff --git a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs index aa4c7c7a4..02a2eb129 100644 --- a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs @@ -15,17 +15,23 @@ namespace Discord.Commands internal class PrimitiveTypeReader : TypeReader { private readonly TryParseDelegate _tryParse; + private readonly float _score; public PrimitiveTypeReader() + : this(1, PrimitiveParsers.Get()) + { } + + public PrimitiveTypeReader(float score, TryParseDelegate tryParse) { - _tryParse = PrimitiveParsers.Get(); + _tryParse = tryParse; + _score = score; } public override Task Read(ICommandContext context, string input) { T value; if (_tryParse(input, out value)) - return Task.FromResult(TypeReaderResult.FromSuccess(value)); + return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score))); return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}")); } } diff --git a/src/Discord.Net.Commands/Results/ParseResult.cs b/src/Discord.Net.Commands/Results/ParseResult.cs index d4a9af521..4d2ecd2b3 100644 --- a/src/Discord.Net.Commands/Results/ParseResult.cs +++ b/src/Discord.Net.Commands/Results/ParseResult.cs @@ -9,34 +9,37 @@ namespace Discord.Commands public IReadOnlyList ArgValues { get; } public IReadOnlyList ParamValues { get; } + public OverloadInfo Overload { get; } + public CommandError? Error { get; } public string ErrorReason { get; } public bool IsSuccess => !Error.HasValue; - private ParseResult(IReadOnlyList argValues, IReadOnlyList paramValues, CommandError? error, string errorReason) + private ParseResult(OverloadInfo overload, IReadOnlyList argValues, IReadOnlyList paramValues, CommandError? error, string errorReason) { + Overload = overload; ArgValues = argValues; ParamValues = paramValues; Error = error; ErrorReason = errorReason; } - public static ParseResult FromSuccess(IReadOnlyList argValues, IReadOnlyList paramValues) + public static ParseResult FromSuccess(OverloadInfo overload, IReadOnlyList argValues, IReadOnlyList paramValues) { for (int i = 0; i < argValues.Count; i++) { if (argValues[i].Values.Count > 1) - return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found."); + return new ParseResult(overload, argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found."); } for (int i = 0; i < paramValues.Count; i++) { if (paramValues[i].Values.Count > 1) - return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found."); + return new ParseResult(overload, argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found."); } - return new ParseResult(argValues, paramValues, null, null); + return new ParseResult(overload, argValues, paramValues, null, null); } - public static ParseResult FromSuccess(IReadOnlyList argValues, IReadOnlyList paramValues) + public static ParseResult FromSuccess(OverloadInfo overload, IReadOnlyList argValues, IReadOnlyList paramValues) { var argList = new TypeReaderResult[argValues.Count]; for (int i = 0; i < argValues.Count; i++) @@ -48,13 +51,13 @@ namespace Discord.Commands for (int i = 0; i < paramValues.Count; i++) paramList[i] = TypeReaderResult.FromSuccess(paramValues[i]); } - return new ParseResult(argList, paramList, null, null); + return new ParseResult(overload, argList, paramList, null, null); } public static ParseResult FromError(CommandError error, string reason) - => new ParseResult(null, null, error, reason); + => new ParseResult(null, null, null, error, reason); public static ParseResult FromError(IResult result) - => new ParseResult(null, null, result.Error, result.ErrorReason); + => new ParseResult(null, null, null, result.Error, result.ErrorReason); public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; private string DebuggerDisplay => IsSuccess ? $"Success ({ArgValues.Count}{(ParamValues.Count > 0 ? $" +{ParamValues.Count} Values" : "")})" : $"{Error}: {ErrorReason}";