-
Notifications
You must be signed in to change notification settings - Fork 383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
more flexible error reporting for SymbolResult #2033
Conversation
|
||
return null; | ||
SymbolResultTree.ReportError(new ParseError(errorMessage, Parent is OptionResult option ? option : this)); | ||
_conversionResult = ArgumentConversionResult.Failure(this, errorMessage, ArgumentConversionResultType.Failed); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at this moment we know that it's a failure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice. As long as we don't later allow clearing of the errors.
...ility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt
Outdated
Show resolved
Hide resolved
Was a test added to pin the expected behavior when |
* rename ReportError to AddError * test coverage improvement (multiple errors for one result)
…one (20% perf gain on simple scenarios)
…should be None improve perf: don't use .Single as it's on hot path and here we know it's only 1 (Arity has been validated)
… tests passing again
remove null checks as it's internal API and we are covered by nullable annotations
internal ParseError? CustomError(Argument argument) | ||
{ | ||
for (var i = 0; i < argument.Validators.Count; i++) | ||
while (tokensToPass > 0 && nextArgumentIndex < arguments.Count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
previously this method was implemented in ParseResultVisitor
, I've moved it here and implemented in a way it does not require an allocation of temporary list
command-line-api/src/System.CommandLine/Parsing/ParseResultVisitor.cs
Lines 251 to 295 in 6524142
var passedOnTokensCount = previousArgumentResult.PassedOnTokens?.Count; | |
if (passedOnTokensCount > 0) | |
{ | |
ShiftPassedOnTokensToNextResult(previousArgumentResult, argumentResults[i], passedOnTokensCount); | |
} | |
} | |
// If this is the current last result but there are more arguments, see if we can shift tokens to the next argument | |
if (commandArgumentResultCount == i) | |
{ | |
var nextArgument = arguments[i]; | |
var nextArgumentResult = new ArgumentResult( | |
nextArgument, | |
_symbolResultTree, | |
_innermostCommandResult); | |
var previousArgumentResult = argumentResults[i - 1]; | |
var passedOnTokensCount = _innermostCommandResult.Tokens.Count; | |
ShiftPassedOnTokensToNextResult(previousArgumentResult, nextArgumentResult, passedOnTokensCount); | |
argumentResults.Add(nextArgumentResult); | |
if (previousArgumentResult.Parent is CommandResult) | |
{ | |
AddToResult(nextArgumentResult); | |
} | |
_symbolResultTree.TryAdd(nextArgumentResult.Argument, nextArgumentResult); | |
} | |
if (commandArgumentResultCount >= argumentResults.Count) | |
{ | |
var argumentResult = argumentResults[i]; | |
ValidateAndConvertArgumentResult(argumentResult); | |
if (argumentResult.PassedOnTokens is { } && | |
i == arguments.Count - 1) | |
{ | |
_unmatchedTokens ??= new List<Token>(); | |
_unmatchedTokens.AddRange(argumentResult.PassedOnTokens); | |
} |
} | ||
|
||
private ArgumentConversionResult Convert(Argument argument) | ||
private ArgumentConversionResult ValidateAndConvert(bool potentialRecursion) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method is now responsible for entire validation. The problem is that it can be called by not only the ParseResultVisitor
but also custom parsers and validator which can do things like ArgumentResult.GetDefaultValue()
. For the latter case I've added potentialRecursion
argument to ensure we don't have endless recursion (validator->getvalue->validator)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name potentialRecursion
is a little unclear to me. Is this saying whether to allow recursion?
=> argumentResult.Argument.HasDefaultValue && argumentResult.Tokens.Count == 0; | ||
|
||
/// <param name="completeValidation">Only the inner most command goes through complete validation.</param> | ||
internal void Validate(bool completeValidation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've moved this logic from the visitor and re-wrote in a way that we don't iterate over every argument more than once (previously we were setting the default values and then validating).
@jonsequitur the PR is ready for review. I hope that you like the refactoring I've made. The side-effects of the refactor is improved performance. For the simple use case Jan is always using it's 1-3% for cold startup time scenario (start a new process, parse the args, print and exit: jkotas/commandline-perf#2), for micro-benchmark it's 20% faster (less important, as it's warm), but the allocations has reduced nicely |
Co-authored-by: Jon Sequeira <jonsequeira@gmail.com>
This required changes in pretty much all of my custom parsers. They were easy to implement, though. |
I've removed
SymbolResult.ErrorMessage
as it did not allow for providing multiple errors for single result and introducedSymbolResult.ReportError
(name can be changed, I don't have strong preferences about it).SymbolResult.ReportError
adds a new error to the list of errors (so multiple errors per result are supported) and in case ofArgumentResult
also setsArgumentConversionResult
(perf, simplicity) and encapsulates the fact thatOption.Argument
is internal and for Arguments owned by Option sets the parent toOptionResult
rather thanArgumentResult
I also did a lot of refactoring related to
ArgumentConversionResult
, I am going to add comments explaining why.