Skip to content

Fix invalid state error when decoding an unparsed optional value #290

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

Merged
merged 1 commit into from
Apr 15, 2021

Conversation

werm098
Copy link
Contributor

@werm098 werm098 commented Mar 15, 2021

Fixes “Internal error. Invalid state while parsing command-line arguments.” that is encountered when an unparsed value is optional.

Root causes:

  • ParsedArgumentsContainer.decodeNil returns false for optional values because it only does a !contains(key) check. This should instead return nil if the value of the element is nil.
  • The decoder did not know about unparsed input origins that and would result in unexpected behavior when decoding nil default values.
  • The value of Mirror.Child is defined as Any but this is confusing because the value could be Optional<Any> which is not equal to nil even when the Optional case is .none.

Checklist

  • I've added at least one test that validates that my change is working, if appropriate
  • I've followed the code style of the rest of the project
  • I've read the Contribution Guidelines
  • I've updated the documentation if necessary

Fixes “Internal error. Invalid state while parsing command-line arguments.” that is encountered when an unparsed value is optional.

Root causes:
- `ParsedArgumentsContainer.decodeNil` returns false for optional values because it only does a `!contains(key)` check. This should instead return nil if the value of the element is nil.
- The decoder did not know about unparsed input origins that and would result in unexpected behavior when decoding nil default values.
- The `value` of `Mirror.Child` is defined as `Any` but this is confusing because the value could be `Optional<Any>` which is not equal to `nil` even when the `Optional` case is `.none`.
/// could be `Optional<Any>` which is not equal to `nil` even when the `Optional` case is `.none`.
///
/// The purpose of this function is to disambiguate when the `value` of a `Mirror.Child` is actaully nil.
static func realValue(for child: Mirror.Child) -> Any? {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This name could probably be better?

Copy link
Member

Choose a reason for hiding this comment

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

I think this would work better as an (Any) -> Any? function, since it doesn't really have anything to do with Mirror, just the fact that Mirror.Child.value is an Any. How about calling it nilOrValue?

/// The `value` of `Mirror.Child` is defined as `Any` but this is confusing because the value
/// could be `Optional<Any>` which is not equal to `nil` even when the `Optional` case is `.none`.
///
/// The purpose of this function is to disambiguate when the `value` of a `Mirror.Child` is actaully nil.
Copy link
Contributor Author

@werm098 werm098 Mar 15, 2021

Choose a reason for hiding this comment

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

Maybe there is a Foundation change we could make too? I have run into this problem previously as well, not sure if this is a known behavior of Mirror.Child or optionals in Swift.

var fullName: String? = "Full Name"
}

fileprivate struct Hogera: ParsableArguments {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we move structs into the extension code block then we could reuse names for different test collections. This could cut down on the amount of metasyntactic type names (and modifications of those names) used.

Copy link
Member

Choose a reason for hiding this comment

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

For sure! I don't think tests are very consistent around this, so if you'd like to structure yours that way, that'd be great.

@natecook1000
Copy link
Member

Thanks for this, @werm098! Do you have a reduced example that shows the incorrect behavior?

@werm098
Copy link
Contributor Author

werm098 commented Mar 18, 2021

Thanks for this, @werm098! Do you have a reduced example that shows the incorrect behavior?

Sure thing, I can create a reduced example project (will post later). I also added to the unit tests for this incorrect behavior too: https://github.com/apple/swift-argument-parser/pull/290/files#diff-aae587b62a491288c0ad8d5dde7f8afd599171fdea4556602063ebe1ccc21259R66

@werm098
Copy link
Contributor Author

werm098 commented Mar 18, 2021

@natecook1000 Minimum reproducing project: MVR.zip

You can change in the dependencies between 0.4.1 and 0.3.2 for an error vs no error respectively.

Copy link
Member

@natecook1000 natecook1000 left a comment

Choose a reason for hiding this comment

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

Sorry for the delay here — this fix looks good! Just a couple nits below. Thanks again for taking this on.

/// could be `Optional<Any>` which is not equal to `nil` even when the `Optional` case is `.none`.
///
/// The purpose of this function is to disambiguate when the `value` of a `Mirror.Child` is actaully nil.
static func realValue(for child: Mirror.Child) -> Any? {
Copy link
Member

Choose a reason for hiding this comment

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

I think this would work better as an (Any) -> Any? function, since it doesn't really have anything to do with Mirror, just the fact that Mirror.Child.value is an Any. How about calling it nilOrValue?

var fullName: String? = "Full Name"
}

fileprivate struct Hogera: ParsableArguments {
Copy link
Member

Choose a reason for hiding this comment

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

For sure! I don't think tests are very consistent around this, so if you'd like to structure yours that way, that'd be great.

@natecook1000
Copy link
Member

@swift-ci Please test

@natecook1000 natecook1000 merged commit bafa74a into apple:main Apr 15, 2021
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.

2 participants