-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
imp: Make clap_derive
call FromStr::from_str
only once per argument.
#2206
Conversation
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.
It feels like you are re-implementing quite a bit of parsing logic in the derive.
IMO the derive should only build the App (with validators), pass it arguments, and structure the result.
But I think, the issue here is happening because we are passing the arguments to the app by calling get_matches_from
instead of try_get_matches_from
which is what you probably want to focus on for the fix (and you did to certain extent).
Could you elaborate on this? As I understand it, if the program uses |
But we have a validator that should have already failed before even from_arg_matches need to run. |
I'm hoping to avoid calling |
I think I understand the context now. But this is not a derive issue. This issue exists even in normal app parsing. As you can see here, we disregard the validated value. And when we send the I do see how this can be an issue. But I don't feel comfortable with the approach you are proposing here. As I said, derive should simply be a wrapper. My gut says we can attack this in derive using |
d892f6d
to
ff7c77d
Compare
As far as I can tell, it does seem to be a derive issue. use std::str::FromStr;
use clap::{App, Arg};
enum Colors {
Red,
Green,
Blue,
}
impl FromStr for Colors {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
eprintln!("called Colors::from_str({})", s);
match s {
"Red" => Ok(Colors::Red),
"Green" => Ok(Colors::Green),
"Blue" => Ok(Colors::Blue),
_ => Err("no match"),
}
}
}
fn main() {
let m = App::new("myapp")
.arg(Arg::from("<color> 'The color to use'"))
.get_matches();
let t = m.value_of_t("color").unwrap_or_else(|e| e.exit());
match t {
Colors::Red => println!("Selected red"),
Colors::Green => println!("Selected green"),
Colors::Blue => println!("Selected blue"),
}
}
For regular non-derive users, this only comes up if a user registers an explicit validator function. And if they do that, then the validator function is called once and the parse function is called once, which unsurprising. For derive users, the derive effectively registers the
Yes, I'm happy to experiment with different approaches. However, I don't yet see a way to do it differently within clap. Derive is registering One option would be to have the validator remember the parsed values, but that looks like it'd be pretty awkward. Another option would be to avoid registering |
So, that is what I was saying. Instead of implementing
|
This does work, however the reason I'm interested in this issue is that I'm writing a library, and I'd like to be able to tell my users they can plug the library's types into
This is counterintuitive to me, and makes derive code default to being different from typical non-derive code, since as far as I can tell validator functions are uncommon in non-derive code. I did some more experimenting, and I found way to restructure the patch to move more of the logic out of the derive, so it's now a net reduction in the lines of code in the derive directory. It refactors the Does this look like a feasible approach? |
This is definitely a much better approach than the previous one, but I am still not happy about removing the Do correct me if I am wrong, I haven't touched it since a long time. |
The |
In fact, if the user specifies a custom validator with clap_derive, it ovewrites the |
434cfbb
to
8b49bb0
Compare
I've now fixed the merge conflicts and rebased this on master. The test failures here also fail for me on master and don't appear to be related to this PR. |
8b49bb0
to
1b0088e
Compare
Rebased, merge conflicts fixed, and now all the tests pass! |
Hey, want to let you know that we will definitely get this fixed before v3 is out. But unfortunately, I am still hesitant about the current design. I would like to take a crack at this but can only do it after some of other things are solved. Please don't think that I have forgotten this or anything. |
1b0088e
to
b8b112b
Compare
Would you be able to say more about your concern here? With what I know right now, removing the
and doesn't appear to have significant downsides, so it's not clear to me whether the concern here is about the removal of the I am considering doing the work in #2298 to split out type inference, which will involve some reorganization, and it would help me to understand how you envision this code eventually being organized. |
I want to see if we can improve the ergonomics for people building libraries on top of clap using traits for the following stuff:
I would like to personally take a crack at this because my instinct says that we can do it better that way. Why would doing #2298 need expanding the code twice? Once for parsing and once for validating? Won't both just use the same auto parser? |
The autoref specialization technique that #2298 uses doesn't work in generic contexts. That is, we can't put it in a function and call it twice; we have to macro-expand it inline every time we need it. It's doable, but it's difficult for me to work on without understanding the purpose of organizing the code this way. |
Understood. In what cases would that increase compilation time and will there be any peformance hit (shouldn't be, I think)? |
I might be missing something fundamental here. Why does it make sense to register After working on the patch here, and seeing how structopt/clap_derive work on the inside, the most likely explanation that I've come up with is that structopt likely started registering The PR here seems to confirm this theory. It makes clap's main API more flexible, with On top of that, many other arrows point in this direction. This is how all hand-written clap code that I've seen works. This is how clap's own examples work. This lets users specify their own validator function without overriding And I can't find any significant downsides. So if there's something I'm missing here, it feels like I need to figure that out first. |
b8b112b
to
da2d630
Compare
da2d630
to
6c6264a
Compare
Currently the way `clap_derive` uses a `from_str` function is to call it once as a validator, discarding the parsed value, and then to call it again to recompute the parsed value. This is unfortunate in cases where `from_str` is expensive or has side effects. This PR changes `clap_derive` to not register `from_str` as a validator so that it doesn't do the first of these two calls. Then, instead of doing `unwrap()` on the other call, it handles the error. This eliminates the redundancy, and also avoids the small performance hit mentioned in [the documentation about validators]. [the documentation about validators]: https://docs.rs/clap-v3/3.0.0-beta.1/clap_v3/struct.Arg.html#method.validator This PR doesn't yet use colorized messages for errors generated during parsing because the `ColorWhen` setting isn't currently available. That's fixable with some refactoring, but I'm interested in getting feedback on the overall approach here first.
6c6264a
to
db8c060
Compare
Currently the way
clap_derive
uses afrom_str
function is to callit once as a validator, discarding the parsed value, and then to call
it again to recompute the parsed value. This is unfortunate in
cases where
from_str
is expensive or has side effects.This PR changes
clap_derive
to not registerfrom_str
as a validatorso that it doesn't do the first of these two calls. Then, instead of
doing
unwrap()
on the other call, it handles the error. This eliminatesthe redundancy, and also avoids the small performance hit mentioned in
the documentation about validators.
This PR doesn't yet use colorized messages for errors generated during
parsing because the
ColorWhen
setting isn't currently available.That's fixable with some refactoring, but I'm interested in getting
feedback on the overall approach here first.