Skip to content
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

Merged
merged 15 commits into from
Feb 2, 2023

Conversation

adamsitnik
Copy link
Member

I've removed SymbolResult.ErrorMessage as it did not allow for providing multiple errors for single result and introduced SymbolResult.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 of ArgumentResult also sets ArgumentConversionResult (perf, simplicity) and encapsulates the fact that Option.Argument is internal and for Arguments owned by Option sets the parent to OptionResult rather than ArgumentResult

I also did a lot of refactoring related to ArgumentConversionResult, I am going to add comments explaining why.


return null;
SymbolResultTree.ReportError(new ParseError(errorMessage, Parent is OptionResult option ? option : this));
_conversionResult = ArgumentConversionResult.Failure(this, errorMessage, ArgumentConversionResultType.Failed);
Copy link
Member Author

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

Copy link
Contributor

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.

@jonsequitur
Copy link
Contributor

Was a test added to pin the expected behavior when ReportError is called more than once?

* rename ReportError to AddError
* test coverage improvement (multiple errors for one result)
* move CommandResult validation logic to CommandResult type
* simplify command handler validation
* don't search for ArgumentResult when we know if up front
…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)
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)
Copy link
Member Author

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

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)
Copy link
Member Author

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)

Copy link
Contributor

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)
Copy link
Member Author

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).

@adamsitnik
Copy link
Member Author

@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>
@KalleOlaviNiemitalo
Copy link

This required changes in pretty much all of my custom parsers. They were easy to implement, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants